blob: 72a7cf9f917835ed3303176e3ca045933ff400c2 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/desks_templates/desks_templates_client.h"
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/desk_template.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/desks/templates/desks_templates_test_util.h"
#include "ash/wm/overview/overview_test_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.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/platform_apps/app_browsertest_util.h"
#include "chrome/browser/ash/app_restore/app_restore_arc_test_helper.h"
#include "chrome/browser/ash/app_restore/app_restore_test_util.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/ui/user_adding_screen.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/desks_templates/desks_templates_app_launch_handler.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/web_applications/system_web_apps/system_web_app_types.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ui/base/window_state_type.h"
#include "components/app_restore/app_launch_info.h"
#include "components/app_restore/features.h"
#include "components/app_restore/full_restore_save_handler.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/restore_data.h"
#include "components/app_restore/window_properties.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/focus_client.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/label_button.h"
#include "url/gurl.h"
using ::testing::_;
using ::testing::ElementsAre;
namespace {
constexpr int32_t kSettingsWindowId = 100;
constexpr char kExampleUrl1[] = "https://examples1.com";
constexpr char kExampleUrl2[] = "https://examples2.com";
constexpr char kExampleUrl3[] = "https://examples3.com";
constexpr char kYoutubeUrl[] = "https://www.youtube.com/";
Browser* FindBrowser(int32_t window_id) {
for (auto* browser : *BrowserList::GetInstance()) {
aura::Window* window = browser->window()->GetNativeWindow();
if (window->GetProperty(app_restore::kRestoreWindowIdKey) == window_id)
return browser;
}
return nullptr;
}
aura::Window* FindBrowserWindow(int32_t window_id) {
Browser* browser = FindBrowser(window_id);
return browser ? browser->window()->GetNativeWindow() : nullptr;
}
std::vector<GURL> GetURLsForBrowserWindow(Browser* browser) {
TabStripModel* tab_strip_model = browser->tab_strip_model();
std::vector<GURL> urls;
for (int i = 0; i < tab_strip_model->count(); ++i)
urls.push_back(tab_strip_model->GetWebContentsAt(i)->GetVisibleURL());
return urls;
}
// TODO(crbug.com/1286515): Remove this. Tests should navigate to overview and
// click the button using an event generator.
std::unique_ptr<ash::DeskTemplate> CaptureActiveDeskAndSaveTemplate() {
base::RunLoop run_loop;
std::unique_ptr<ash::DeskTemplate> desk_template;
DesksTemplatesClient::Get()->CaptureActiveDeskAndSaveTemplate(
base::BindLambdaForTesting(
[&](std::unique_ptr<ash::DeskTemplate> captured_desk_template,
std::string error_string) {
run_loop.Quit();
ASSERT_TRUE(captured_desk_template);
desk_template = std::move(captured_desk_template);
}));
run_loop.Run();
return desk_template;
}
std::string GetTemplateJson(const std::string& uuid, Profile* profile) {
base::RunLoop run_loop;
std::string template_json_result;
DesksTemplatesClient::Get()->GetTemplateJson(
uuid, profile,
base::BindLambdaForTesting(
[&](const std::string& template_json, std::string error_string) {
run_loop.Quit();
ASSERT_TRUE(error_string.empty());
template_json_result = template_json;
}));
run_loop.Run();
return template_json_result;
}
void DeleteDeskTemplate(const base::GUID uuid) {
base::RunLoop run_loop;
DesksTemplatesClient::Get()->DeleteDeskTemplate(
uuid.AsLowercaseString(),
base::BindLambdaForTesting(
[&](std::string error_string) { run_loop.Quit(); }));
run_loop.Run();
}
web_app::AppId CreateSettingsSystemWebApp(Profile* profile) {
web_app::WebAppProvider::GetForTest(profile)
->system_web_app_manager()
.InstallSystemAppsForTesting();
web_app::AppId settings_app_id = *web_app::GetAppIdForSystemWebApp(
profile, web_app::SystemAppType::SETTINGS);
apps::AppLaunchParams params(
settings_app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW, apps::mojom::LaunchSource::kFromTest);
params.restore_id = kSettingsWindowId;
apps::AppServiceProxyFactory::GetForProfile(profile)
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(params));
web_app::FlushSystemWebAppLaunchesForTesting(profile);
return settings_app_id;
}
void ClickButton(const views::Button* button) {
DCHECK(button);
DCHECK(button->GetVisible());
aura::Window* root_window =
button->GetWidget()->GetNativeWindow()->GetRootWindow();
ui::test::EventGenerator event_generator(root_window);
event_generator.MoveMouseTo(button->GetBoundsInScreen().CenterPoint());
event_generator.ClickLeftButton();
}
// If `wait_for_ui` is true, wait for the callback from the model to update the
// UI.
void ClickSaveDeskAsTemplateButton(bool wait_for_ui) {
views::Button* save_desk_as_template_button =
ash::GetSaveDeskAsTemplateButton();
DCHECK(save_desk_as_template_button);
ClickButton(save_desk_as_template_button);
if (wait_for_ui)
ash::WaitForDesksTemplatesUI();
}
void ClickSaveDeskAsTemplateButton() {
ClickSaveDeskAsTemplateButton(/*wait_for_ui=*/true);
}
void ClickZeroStateTemplatesButton() {
views::Button* zero_state_templates_button =
ash::GetZeroStateDesksTemplatesButton();
ASSERT_TRUE(zero_state_templates_button);
ClickButton(zero_state_templates_button);
ash::WaitForDesksTemplatesUI();
}
void ClickExpandedStateTemplatesButton() {
views::Button* expanded_state_templates_button =
ash::GetExpandedStateDesksTemplatesButton();
ASSERT_TRUE(expanded_state_templates_button);
ClickButton(expanded_state_templates_button);
ash::WaitForDesksTemplatesUI();
}
void ClickFirstTemplateItem() {
views::Button* template_item = ash::GetTemplateItemButton(/*index=*/0);
DCHECK(template_item);
ClickButton(template_item);
// We need to wait for the template to be fetched from the model.
ash::WaitForDesksTemplatesUI();
}
const std::vector<ash::DeskTemplate*> GetAllEntries() {
std::vector<ash::DeskTemplate*> templates;
base::RunLoop loop;
DesksTemplatesClient::Get()->GetDeskModel()->GetAllEntries(
base::BindLambdaForTesting(
[&](desks_storage::DeskModel::GetAllEntriesStatus status,
const std::vector<ash::DeskTemplate*>& entries) {
DCHECK_EQ(desks_storage::DeskModel::GetAllEntriesStatus::kOk,
status);
templates = entries;
loop.Quit();
}));
loop.Run();
return templates;
}
class MockDesksTemplatesAppLaunchHandler
: public DesksTemplatesAppLaunchHandler {
public:
explicit MockDesksTemplatesAppLaunchHandler(Profile* profile)
: DesksTemplatesAppLaunchHandler(profile) {}
MockDesksTemplatesAppLaunchHandler(
const MockDesksTemplatesAppLaunchHandler&) = delete;
MockDesksTemplatesAppLaunchHandler& operator=(
const MockDesksTemplatesAppLaunchHandler&) = delete;
~MockDesksTemplatesAppLaunchHandler() override = default;
MOCK_METHOD(void,
LaunchSystemWebAppOrChromeApp,
(apps::mojom::AppType,
const std::string&,
const app_restore::RestoreData::LaunchList&),
(override));
};
} // namespace
// Scoped class that temporarily sets a new app launch handler for testing
// purposes.
class ScopedDesksTemplatesAppLaunchHandlerSetter {
public:
explicit ScopedDesksTemplatesAppLaunchHandlerSetter(
std::unique_ptr<DesksTemplatesAppLaunchHandler> launch_handler) {
DCHECK_EQ(0, instance_count_);
++instance_count_;
DesksTemplatesClient* desks_client = DesksTemplatesClient::Get();
DCHECK(desks_client);
old_app_launch_handler_ = std::move(desks_client->app_launch_handler_);
desks_client->app_launch_handler_ = std::move(launch_handler);
}
ScopedDesksTemplatesAppLaunchHandlerSetter(
const ScopedDesksTemplatesAppLaunchHandlerSetter&) = delete;
ScopedDesksTemplatesAppLaunchHandlerSetter& operator=(
const ScopedDesksTemplatesAppLaunchHandlerSetter&) = delete;
~ScopedDesksTemplatesAppLaunchHandlerSetter() {
DCHECK_EQ(1, instance_count_);
--instance_count_;
DesksTemplatesClient* desks_client = DesksTemplatesClient::Get();
DCHECK(desks_client);
desks_client->app_launch_handler_ = std::move(old_app_launch_handler_);
}
private:
// Variable to ensure we never have more than one instance of this object.
static int instance_count_;
// The old app launch handler prior to the object being created. May be
// nullptr.
std::unique_ptr<DesksTemplatesAppLaunchHandler> old_app_launch_handler_;
};
int ScopedDesksTemplatesAppLaunchHandlerSetter::instance_count_ = 0;
class DesksTemplatesClientTest : public extensions::PlatformAppBrowserTest {
public:
DesksTemplatesClientTest() {
// This feature depends on full restore feature, so need to enable it.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{full_restore::features::kFullRestore,
ash::features::kDesksTemplates},
/*disabled_features=*/{ash::features::kDeskTemplateSync});
}
DesksTemplatesClientTest(const DesksTemplatesClientTest&) = delete;
DesksTemplatesClientTest& operator=(const DesksTemplatesClientTest&) = delete;
~DesksTemplatesClientTest() override = default;
void SetTemplate(std::unique_ptr<ash::DeskTemplate> launch_template) {
DesksTemplatesClient::Get()->launch_template_for_test_ =
std::move(launch_template);
}
void LaunchTemplate(const base::GUID& uuid) {
DesksTemplatesClient::Get()->LaunchDeskTemplate(uuid.AsLowercaseString(),
base::DoNothing());
}
void SetAndLaunchTemplate(std::unique_ptr<ash::DeskTemplate> desk_template) {
ash::DeskTemplate* desk_template_ptr = desk_template.get();
SetTemplate(std::move(desk_template));
LaunchTemplate(desk_template_ptr->uuid());
}
Browser* CreateBrowser(const std::vector<GURL>& urls,
absl::optional<int> active_url_index = absl::nullopt) {
Browser::CreateParams params(Browser::TYPE_NORMAL, profile(),
/*user_gesture=*/false);
Browser* browser = Browser::Create(params);
// Create a new tab and make sure the urls have loaded.
for (int i = 0; i < urls.size(); i++) {
content::TestNavigationObserver navigation_observer(urls[i]);
navigation_observer.StartWatchingNewWebContents();
chrome::AddTabAt(
browser, urls[i], /*index=*/-1,
/*foreground=*/!active_url_index || active_url_index.value() == i);
navigation_observer.Wait();
}
browser->window()->Show();
return browser;
}
Browser* InstallAndLaunchPWA(const GURL& start_url, bool launch_in_browser) {
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->start_url = start_url;
web_app_info->scope = start_url.GetWithoutFilename();
if (!launch_in_browser)
web_app_info->user_display_mode = blink::mojom::DisplayMode::kStandalone;
web_app_info->title = u"A Web App";
const web_app::AppId app_id =
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
// Wait for app service to see the newly installed app.
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
proxy->FlushMojoCallsForTesting();
return launch_in_browser
? web_app::LaunchBrowserForWebAppInTab(profile(), app_id)
: web_app::LaunchWebAppBrowserAndWait(profile(), app_id);
}
// extensions::PlatformAppBrowserTest:
void SetUpOnMainThread() override {
::full_restore::SetActiveProfilePath(profile()->GetPath());
extensions::PlatformAppBrowserTest::SetUpOnMainThread();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that a browser's urls can be captured correctly in the desk template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, CaptureBrowserUrlsTest) {
// Create a new browser and add a few tabs to it.
Browser* browser = CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
aura::Window* window = browser->window()->GetNativeWindow();
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Get current tabs from browser.
std::vector<GURL> urls = GetURLsForBrowserWindow(browser);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
// Find |browser| window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(browser_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
const auto& data = app_restore_data_iter->second;
// Check the urls are captured correctly in the |desk_template|.
EXPECT_EQ(data->urls.value(), urls);
}
// Tests that incognito browser windows will NOT be captured in the desk
// template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, CaptureIncognitoBrowserTest) {
Browser* incognito_browser = CreateIncognitoBrowser();
chrome::AddTabAt(incognito_browser, GURL(kExampleUrl1), /*index=*/-1,
/*foreground=*/true);
chrome::AddTabAt(incognito_browser, GURL(kExampleUrl2), /*index=*/-1,
/*foreground=*/true);
incognito_browser->window()->Show();
aura::Window* window = incognito_browser->window()->GetNativeWindow();
const int32_t incognito_browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
// Find |browser| window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(incognito_browser_window_id);
// Created incognito window is NOT in restore list
ASSERT_TRUE(app_restore_data_iter == iter->second.end());
}
// Tests that browsers and chrome apps can be captured correctly in the desk
// template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
CaptureActiveDeskAsTemplateTest) {
// Test that Singleton was properly initialized.
ASSERT_TRUE(DesksTemplatesClient::Get());
// Change |browser|'s bounds.
const gfx::Rect browser_bounds = gfx::Rect(0, 0, 800, 200);
aura::Window* window = browser()->window()->GetNativeWindow();
window->SetBounds(browser_bounds);
// Make window visible on all desks.
window->SetProperty(aura::client::kWindowWorkspaceKey,
aura::client::kWindowWorkspaceVisibleOnAllWorkspaces);
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Create the settings app, which is a system web app.
web_app::AppId settings_app_id =
CreateSettingsSystemWebApp(browser()->profile());
// Change the Settings app's bounds too.
const gfx::Rect settings_app_bounds = gfx::Rect(100, 100, 800, 300);
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
const int32_t settings_window_id =
settings_window->GetProperty(app_restore::kWindowIdKey);
ASSERT_TRUE(settings_window);
settings_window->SetBounds(settings_app_bounds);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Test the default template's name is the current desk's name.
auto* desks_controller = ash::DesksController::Get();
EXPECT_EQ(
desk_template->template_name(),
desks_controller->GetDeskName(desks_controller->GetActiveDeskIndex()));
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 2u);
// Find |browser| window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(browser_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
const auto& data = app_restore_data_iter->second;
// Verify window info are correctly captured.
EXPECT_EQ(browser_bounds, data->current_bounds.value());
// `visible_on_all_workspaces` should have been reset even though
// the captured window is visible on all workspaces.
EXPECT_FALSE(data->desk_id.has_value());
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(screen->GetDisplayNearestWindow(window).id(),
data->display_id.value());
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
// We don't capture the window's desk_id as a template will always
// create in a new desk.
EXPECT_FALSE(data->desk_id.has_value());
// Find Setting app's app restore data.
auto iter2 = app_id_to_launch_list.find(settings_app_id);
ASSERT_TRUE(iter2 != app_id_to_launch_list.end());
auto app_restore_data_iter2 = iter2->second.find(settings_window_id);
ASSERT_TRUE(app_restore_data_iter2 != iter2->second.end());
const auto& data2 = app_restore_data_iter2->second;
EXPECT_EQ(
static_cast<int>(apps::mojom::LaunchContainer::kLaunchContainerWindow),
data2->container.value());
EXPECT_EQ(static_cast<int>(WindowOpenDisposition::NEW_WINDOW),
data2->disposition.value());
// Verify window info are correctly captured.
EXPECT_EQ(settings_app_bounds, data2->current_bounds.value());
EXPECT_FALSE(data2->desk_id.has_value());
EXPECT_EQ(screen->GetDisplayNearestWindow(window).id(),
data->display_id.value());
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
EXPECT_FALSE(data2->desk_id.has_value());
}
// Tests that launching the same desk template multiple times creates desks with
// different/incremented names.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, LaunchMultipleDeskTemplates) {
const base::GUID kDeskUuid = base::GUID::GenerateRandomV4();
const std::u16string kDeskName(u"Test Desk Name");
auto* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// TODO(crbug.com/1273532): Note that `SetTemplate` allows setting an empty
// desk template which shouldn't be possible in a real workflow. Make sure a
// non empty desks are launched when this test is updated to use the real
// workflow.
auto desk_template = std::make_unique<ash::DeskTemplate>(
kDeskUuid.AsLowercaseString(), ash::DeskTemplateSource::kUser,
base::UTF16ToUTF8(kDeskName), base::Time::Now());
SetTemplate(std::move(desk_template));
auto check_launch_template_desk_name =
[kDeskUuid, desks_controller, this](const std::u16string& desk_name) {
LaunchTemplate(kDeskUuid);
EXPECT_EQ(desk_name, desks_controller->GetDeskName(
desks_controller->GetActiveDeskIndex()));
};
// Launching a desk from the template creates a desk with the same name as the
// template.
check_launch_template_desk_name(kDeskName);
// Launch more desks from the template and verify that the newly created desks
// have unique names.
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (1)"));
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (2)"));
// Remove "Test Desk Name (1)", which means the next created desk from
// template will have that name. Then it will skip (2) since it already
// exists, and create the next desk with (3).
RemoveDesk(desks_controller->desks()[2].get());
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (1)"));
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (3)"));
// Same as above, but make sure that deleting the desk with the exact template
// name still functions the same by only filling in whatever name is
// available.
RemoveDesk(desks_controller->desks()[1].get());
check_launch_template_desk_name(kDeskName);
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (4)"));
}
// Tests that launching a template that contains a system web app works as
// expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, LaunchTemplateWithSystemApp) {
ASSERT_TRUE(DesksTemplatesClient::Get());
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(browser()->profile());
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
ASSERT_TRUE(settings_window);
const std::u16string settings_title = settings_window->GetTitle();
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Close the settings window. We'll need to verify if it reopens later.
views::Widget* settings_widget =
views::Widget::GetWidgetForNativeWindow(settings_window);
settings_widget->CloseNow();
ASSERT_FALSE(FindBrowserWindow(kSettingsWindowId));
settings_window = nullptr;
auto* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// Set the template we created as the template we want to launch.
SetAndLaunchTemplate(std::move(desk_template));
// Verify that the settings window has been launched on the new desk (desk B).
// TODO(sammiequon): Right now the app just launches, so verify the title
// matches. We should verify the restore id and use
// `FindBrowserWindow(kSettingsWindowId)` once things are wired up properly.
EXPECT_EQ(1, desks_controller->GetActiveDeskIndex());
for (auto* browser : *BrowserList::GetInstance()) {
aura::Window* window = browser->window()->GetNativeWindow();
if (window->GetTitle() == settings_title) {
settings_window = window;
break;
}
}
ASSERT_TRUE(settings_window);
EXPECT_EQ(ash::Shell::GetContainer(settings_window->GetRootWindow(),
ash::kShellWindowId_DeskContainerB),
settings_window->parent());
}
// Tests that launching a template that contains a system web app will move the
// existing instance of the system web app to the current desk.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
LaunchTemplateWithSystemAppExisting) {
ASSERT_TRUE(DesksTemplatesClient::Get());
Profile* profile = browser()->profile();
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(profile);
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
ASSERT_TRUE(settings_window);
EXPECT_EQ(2u, BrowserList::GetInstance()->size());
// Give the settings app a known position.
const gfx::Rect settings_bounds(100, 100, 600, 400);
settings_window->SetBounds(settings_bounds);
// Focus the browser so that the settings window is stacked at the bottom.
browser()->window()->GetNativeWindow()->Focus();
ASSERT_THAT(settings_window->parent()->children(),
ElementsAre(settings_window, _));
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Move the settings window to a new place and stack it on top so that we can
// later verify that it has been placed and stacked correctly.
settings_window->SetBounds(gfx::Rect(150, 150, 650, 500));
settings_window->Focus();
ash::DesksController* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// Set the template we created as the template we want to launch.
SetAndLaunchTemplate(std::move(desk_template));
// We launch a new browser window, but not a new settings app. Verify that the
// window has been moved to the right place and stacked at the bottom.
EXPECT_EQ(3u, BrowserList::GetInstance()->size());
EXPECT_TRUE(desks_controller->BelongsToActiveDesk(settings_window));
EXPECT_EQ(settings_bounds, settings_window->bounds());
ASSERT_THAT(settings_window->parent()->children(),
ElementsAre(settings_window, _));
}
// Tests that launching a template that contains a chrome app works as expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, LaunchTemplateWithChromeApp) {
DesksTemplatesClient* desks_client = DesksTemplatesClient::Get();
ASSERT_TRUE(desks_client);
// Create a chrome app.
const extensions::Extension* extension =
LoadAndLaunchPlatformApp("launch", "Launched");
ASSERT_TRUE(extension);
const std::string extension_id = extension->id();
::full_restore::SaveAppLaunchInfo(
profile()->GetPath(),
std::make_unique<app_restore::AppLaunchInfo>(
extension_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW, display::kDefaultDisplayId,
std::vector<base::FilePath>{}, nullptr));
extensions::AppWindow* app_window = CreateAppWindow(profile(), extension);
ASSERT_TRUE(app_window);
ASSERT_TRUE(GetFirstAppWindowForApp(extension_id));
// Capture the active desk, which contains the chrome app.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
// Close the chrome app window. We'll need to verify if it reopens later.
views::Widget* app_widget =
views::Widget::GetWidgetForNativeWindow(app_window->GetNativeWindow());
app_widget->CloseNow();
ASSERT_FALSE(GetFirstAppWindowForApp(extension_id));
ash::DesksController* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// `BrowserAppLauncher::LaunchAppWithParams()` does not launch the chrome app
// in tests, so here we set up a mock app launch handler and just verify a
// `LaunchSystemWebAppOrChromeApp()` call with the associated extension is
// seen.
auto mock_app_launch_handler =
std::make_unique<MockDesksTemplatesAppLaunchHandler>(profile());
MockDesksTemplatesAppLaunchHandler* mock_app_launch_handler_ptr =
mock_app_launch_handler.get();
ScopedDesksTemplatesAppLaunchHandlerSetter scoped_launch_handler(
std::move(mock_app_launch_handler));
EXPECT_CALL(*mock_app_launch_handler_ptr,
LaunchSystemWebAppOrChromeApp(_, extension_id, _));
// Set the template we created as the template we want to launch.
SetAndLaunchTemplate(std::move(desk_template));
}
// Tests that launching a template that contains a browser window works as
// expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
LaunchTemplateWithBrowserWindow) {
ASSERT_TRUE(DesksTemplatesClient::Get());
// Create a new browser and add a few tabs to it, and specify the active tab
// index.
const int browser_active_index = 1;
Browser* browser = CreateBrowser(
{GURL(kExampleUrl1), GURL(kExampleUrl2), GURL(kExampleUrl3)},
/*active_url_index=*/browser_active_index);
// Verify that the active tab is correct.
EXPECT_EQ(browser_active_index, browser->tab_strip_model()->active_index());
aura::Window* window = browser->window()->GetNativeWindow();
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Get current tabs from browser.
const std::vector<GURL> urls = GetURLsForBrowserWindow(browser);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
ash::DesksController* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// Set the template we created as the template we want to launch.
SetAndLaunchTemplate(std::move(desk_template));
// Wait for the tabs to load.
content::RunAllTasksUntilIdle();
// Verify that the browser was launched with the correct urls and active tab.
Browser* new_browser = FindBrowser(browser_window_id);
ASSERT_TRUE(new_browser);
EXPECT_EQ(urls, GetURLsForBrowserWindow(new_browser));
EXPECT_EQ(browser_active_index,
new_browser->tab_strip_model()->active_index());
// Verify that the browser window has been launched on the new desk (desk B).
EXPECT_EQ(1, desks_controller->GetActiveDeskIndex());
aura::Window* browser_window = new_browser->window()->GetNativeWindow();
ASSERT_TRUE(browser_window);
EXPECT_EQ(ash::Shell::GetContainer(browser_window->GetRootWindow(),
ash::kShellWindowId_DeskContainerB),
browser_window->parent());
}
// Tests that browser session restore isn't triggered when we launch a template
// that contains a browser window.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
PreventBrowserSessionRestoreTest) {
ASSERT_TRUE(DesksTemplatesClient::Get());
// Do not exit from test or delete the Profile* when last browser is closed.
ScopedKeepAlive keep_alive(KeepAliveOrigin::BROWSER,
KeepAliveRestartOption::DISABLED);
ScopedProfileKeepAlive profile_keep_alive(
browser()->profile(), ProfileKeepAliveOrigin::kBrowserWindow);
// Enable session service.
SessionStartupPref pref(SessionStartupPref::LAST);
Profile* profile = browser()->profile();
SessionStartupPref::SetStartupPref(profile, pref);
const int expected_tab_count = 2;
chrome::AddTabAt(browser(), GURL(kExampleUrl2), /*index=*/-1,
/*foreground=*/true);
EXPECT_EQ(expected_tab_count, browser()->tab_strip_model()->count());
const int32_t browser_window_id =
browser()->window()->GetNativeWindow()->GetProperty(
app_restore::kWindowIdKey);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
// Close the browser and verify that all browser windows are closed.
CloseBrowserSynchronously(browser());
EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
// Set the template we created and launch the template.
SetAndLaunchTemplate(std::move(desk_template));
// Verify that the browser was launched with the correct number of tabs, and
// that browser session restore did not restore any windows/tabs.
Browser* new_browser = FindBrowser(browser_window_id);
ASSERT_TRUE(new_browser);
EXPECT_EQ(expected_tab_count, GetURLsForBrowserWindow(new_browser).size());
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
}
// Tests that the windows and tabs count histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
DeskTemplateWindowAndTabCountHistogram) {
ASSERT_TRUE(DesksTemplatesClient::Get());
base::HistogramTester histogram_tester;
Profile* profile = browser()->profile();
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(profile);
CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2), GURL(kExampleUrl3)});
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 2u);
constexpr char kWindowCountHistogramName[] = "Ash.DeskTemplate.WindowCount";
constexpr char kTabCountHistogramName[] = "Ash.DeskTemplate.TabCount";
constexpr char kWindowAndTabCountHistogramName[] =
"Ash.DeskTemplate.WindowAndTabCount";
// NOTE: there is an existing browser with 1 tab created by BrowserMain().
histogram_tester.ExpectBucketCount(kWindowCountHistogramName, 4, 1);
histogram_tester.ExpectBucketCount(kTabCountHistogramName, 6, 1);
histogram_tester.ExpectBucketCount(kWindowAndTabCountHistogramName, 7, 1);
}
// Tests that the launch from template histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
DeskTemplateLaunchFromTemplateHistogram) {
ASSERT_TRUE(DesksTemplatesClient::Get());
base::HistogramTester histogram_tester;
// Create a new browser.
CreateBrowser({});
// Save the template.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
ASSERT_TRUE(desk_template);
// Set the template we created as the template we want to launch.
ash::DeskTemplate* desk_template_ptr = desk_template.get();
SetTemplate(std::move(desk_template));
int launches = 5;
for (int i = 0; i < launches; i++)
LaunchTemplate(desk_template_ptr->uuid());
constexpr char kLaunchFromTemplateHistogramName[] =
"Ash.DeskTemplate.LaunchFromTemplate";
histogram_tester.ExpectTotalCount(kLaunchFromTemplateHistogramName, launches);
}
// Tests that the template count histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
DeskTemplateUserTemplateCountHistogram) {
ASSERT_TRUE(DesksTemplatesClient::Get());
base::HistogramTester histogram_tester;
// Verify that all template saves and deletes are captured by the histogram.
CaptureActiveDeskAndSaveTemplate();
CaptureActiveDeskAndSaveTemplate();
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
DeleteDeskTemplate(desk_template->uuid());
CaptureActiveDeskAndSaveTemplate();
constexpr char kUserTemplateCountHistogramName[] =
"Ash.DeskTemplate.UserTemplateCount";
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 1, 1);
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 2, 2);
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 3, 2);
}
// Tests that browser windows created from a template have the correct bounds
// and window state.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, BrowserWindowRestorationTest) {
ASSERT_TRUE(DesksTemplatesClient::Get());
// Create a new browser and set its bounds.
Browser* browser_1 = CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
const gfx::Rect browser_bounds_1 = gfx::Rect(100, 100, 600, 200);
aura::Window* window_1 = browser_1->window()->GetNativeWindow();
window_1->SetBounds(browser_bounds_1);
// Create a new minimized browser.
Browser* browser_2 = CreateBrowser({GURL(kExampleUrl1)});
const gfx::Rect browser_bounds_2 = gfx::Rect(150, 150, 500, 300);
aura::Window* window_2 = browser_2->window()->GetNativeWindow();
window_2->SetBounds(browser_bounds_2);
EXPECT_EQ(browser_bounds_2, window_2->bounds());
browser_2->window()->Minimize();
// Create a new maximized browser.
Browser* browser_3 = CreateBrowser({GURL(kExampleUrl1)});
browser_3->window()->Maximize();
EXPECT_EQ(browser_bounds_1, window_1->bounds());
EXPECT_EQ(browser_bounds_2, window_2->bounds());
ASSERT_TRUE(browser_2->window()->IsMinimized());
ASSERT_TRUE(browser_3->window()->IsMaximized());
const int32_t browser_window_id_1 =
window_1->GetProperty(app_restore::kWindowIdKey);
const int32_t browser_window_id_2 =
window_2->GetProperty(app_restore::kWindowIdKey);
const int32_t browser_window_id_3 =
browser_3->window()->GetNativeWindow()->GetProperty(
app_restore::kWindowIdKey);
// Capture the active desk, which contains the two browser windows.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Set the template and launch it.
SetAndLaunchTemplate(std::move(desk_template));
// Verify that the browser was launched with the correct bounds.
Browser* new_browser_1 = FindBrowser(browser_window_id_1);
ASSERT_TRUE(new_browser_1);
EXPECT_EQ(browser_bounds_1,
new_browser_1->window()->GetNativeWindow()->bounds());
// Verify that the browser was launched and minimized.
Browser* new_browser_2 = FindBrowser(browser_window_id_2);
ASSERT_TRUE(new_browser_2);
ASSERT_TRUE(new_browser_2->window()->IsMinimized());
EXPECT_EQ(browser_bounds_2,
new_browser_2->window()->GetNativeWindow()->bounds());
// Verify that the browser was launched and maximized.
Browser* new_browser_3 = FindBrowser(browser_window_id_3);
ASSERT_TRUE(new_browser_3);
ASSERT_TRUE(new_browser_3->window()->IsMaximized());
}
// Tests that saving and launching a template that contains a PWA works as
// expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, LaunchTemplateWithPWA) {
ASSERT_TRUE(DesksTemplatesClient::Get());
Browser* pwa_browser =
InstallAndLaunchPWA(GURL(kExampleUrl1), /*launch_in_browser=*/false);
ASSERT_TRUE(pwa_browser->is_type_app());
aura::Window* pwa_window = pwa_browser->window()->GetNativeWindow();
const gfx::Rect pwa_bounds(50, 50, 500, 500);
pwa_window->SetBounds(pwa_bounds);
const int32_t pwa_window_id =
pwa_window->GetProperty(app_restore::kWindowIdKey);
const std::string* app_name =
pwa_window->GetProperty(app_restore::kBrowserAppNameKey);
ASSERT_TRUE(app_name);
// Capture the active desk, which contains the PWA.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Find |pwa_browser| window's app restore data.
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
ASSERT_TRUE(restore_data->HasAppTypeBrowser());
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(pwa_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
const auto& data = app_restore_data_iter->second;
// Verify window info are correctly captured.
EXPECT_EQ(pwa_bounds, data->current_bounds.value());
ASSERT_TRUE(data->app_type_browser.has_value() &&
data->app_type_browser.value());
EXPECT_EQ(*app_name, *data->app_name);
// Set the template and launch it.
SetAndLaunchTemplate(std::move(desk_template));
// Verify that the PWA was launched correctly.
Browser* new_pwa_browser = FindBrowser(pwa_window_id);
ASSERT_TRUE(new_pwa_browser);
ASSERT_TRUE(new_pwa_browser->is_type_app());
aura::Window* new_browser_window =
new_pwa_browser->window()->GetNativeWindow();
EXPECT_NE(new_browser_window, pwa_window);
EXPECT_EQ(pwa_bounds, new_browser_window->bounds());
const std::string* new_app_name =
new_browser_window->GetProperty(app_restore::kBrowserAppNameKey);
ASSERT_TRUE(new_app_name);
EXPECT_EQ(*app_name, *new_app_name);
}
// Tests that saving and launching a template that contains a PWA in a browser
// window works as expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
LaunchTemplateWithPWAInBrowser) {
ASSERT_TRUE(DesksTemplatesClient::Get());
Browser* pwa_browser =
InstallAndLaunchPWA(GURL(kYoutubeUrl), /*launch_in_browser=*/true);
aura::Window* pwa_window = pwa_browser->window()->GetNativeWindow();
const int32_t pwa_window_id =
pwa_window->GetProperty(app_restore::kWindowIdKey);
// Capture the active desk, which contains the PWA.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
// Test that |pwa_browser| restore data can be found.
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
// Test that |pwa_browser|'s restore data is saved under the Chrome browser
// app id extension_misc::kChromeAppId, not Youtube app id
// extension_misc::kYoutubeAppId.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(pwa_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
iter = app_id_to_launch_list.find(extension_misc::kYoutubeAppId);
EXPECT_FALSE(iter != app_id_to_launch_list.end());
}
// Tests that captured desk templates can be recalled as a JSON string.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, GetDeskTemplateJson) {
// Test that Singleton was properly initialized.
ASSERT_TRUE(DesksTemplatesClient::Get());
// Change |browser|'s bounds.
const gfx::Rect browser_bounds = gfx::Rect(0, 0, 800, 200);
aura::Window* window = browser()->window()->GetNativeWindow();
window->SetBounds(browser_bounds);
// Make window visible on all desks.
window->SetProperty(aura::client::kWindowWorkspaceKey,
aura::client::kWindowWorkspaceVisibleOnAllWorkspaces);
// Create the settings app, which is a system web app.
web_app::AppId settings_app_id =
CreateSettingsSystemWebApp(browser()->profile());
// Change the Settings app's bounds too.
const gfx::Rect settings_app_bounds = gfx::Rect(100, 100, 800, 300);
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
ASSERT_TRUE(settings_window);
settings_window->SetBounds(settings_app_bounds);
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
std::string template_json = GetTemplateJson(
desk_template->uuid().AsLowercaseString(), browser()->profile());
// content of the conversion is tested in:
// components/desks_storage/core/desk_template_conversion_unittests.cc in this
// case we're simply interested in whether or not we got content back.
ASSERT_TRUE(!template_json.empty());
}
// Tests that basic operations using the native UI work as expected.
// TODO(crbug.com/1286515): Remove the NativeUI prefix from these tests. Remove
// the tests that do not have the NativeUI prefix other than GetDeskTemplateJson
// once the extension is deprecated.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, NativeUIBasic) {
auto* desk_model = DesksTemplatesClient::Get()->GetDeskModel();
ASSERT_EQ(0, desk_model->GetEntryCount());
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
// Tests that since we have no templates right now, so the desks templates
// button is hidden.
views::Button* zero_state_templates_button =
ash::GetZeroStateDesksTemplatesButton();
ASSERT_TRUE(zero_state_templates_button);
EXPECT_FALSE(zero_state_templates_button->GetVisible());
// Note that this button needs at least one window to show up. Browser tests
// have an existing browser window, so no new window needs to be created.
views::Button* save_desk_as_template_button =
ash::GetSaveDeskAsTemplateButton();
ASSERT_TRUE(save_desk_as_template_button);
ClickButton(save_desk_as_template_button);
ash::WaitForDesksTemplatesUI();
EXPECT_EQ(1, desk_model->GetEntryCount());
// Tests that since we have one template right now, so that the expanded state
// desk button is shown, and the desk templates grid has one item.
auto* expanded_state_templates_button =
ash::GetExpandedStateDesksTemplatesButton();
ASSERT_TRUE(expanded_state_templates_button);
EXPECT_TRUE(expanded_state_templates_button->GetVisible());
views::Button* template_item = ash::GetTemplateItemButton(/*index=*/0);
EXPECT_TRUE(template_item);
}
// Tests launching a template with a browser window.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest, NativeUILaunchBrowser) {
// Create a new browser and add a few tabs to it, and specify the active tab
// index.
const int browser_active_index = 1;
Browser* browser = CreateBrowser(
{GURL(kExampleUrl1), GURL(kExampleUrl2), GURL(kExampleUrl3)},
/*active_url_index=*/browser_active_index);
// Verify that the active tab is correct.
EXPECT_EQ(browser_active_index, browser->tab_strip_model()->active_index());
aura::Window* window = browser->window()->GetNativeWindow();
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Get current tabs from browser.
const std::vector<GURL> urls = GetURLsForBrowserWindow(browser);
// There are two browser windows currently, the default one and the one we
// just created.
ASSERT_EQ(2u, BrowserList::GetInstance()->size());
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
ClickFirstTemplateItem();
// Wait for the tabs to load.
content::RunAllTasksUntilIdle();
// There are a total of four browser windows now. The two initial ones and the
// two created from our template.
EXPECT_EQ(4u, BrowserList::GetInstance()->size());
// Test that the created browser has the same tabs and the same active tab.
Browser* new_browser = FindBrowser(browser_window_id);
ASSERT_TRUE(new_browser);
EXPECT_EQ(urls, GetURLsForBrowserWindow(new_browser));
EXPECT_EQ(browser_active_index,
new_browser->tab_strip_model()->active_index());
// Verify that the browser window has been launched on the new desk (desk B).
EXPECT_EQ(1, ash::DesksController::Get()->GetActiveDeskIndex());
aura::Window* browser_window = new_browser->window()->GetNativeWindow();
ASSERT_TRUE(browser_window);
EXPECT_EQ(ash::Shell::GetContainer(browser_window->GetRootWindow(),
ash::kShellWindowId_DeskContainerB),
browser_window->parent());
}
// Tests that a browser's urls can be captured correctly in the desk template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUICaptureBrowserUrlsTest) {
// Create a new browser and add a few tabs to it.
Browser* browser = CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
aura::Window* window = browser->window()->GetNativeWindow();
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Get current tabs from browser.
std::vector<GURL> urls = GetURLsForBrowserWindow(browser);
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
std::vector<ash::DeskTemplate*> templates = GetAllEntries();
ASSERT_EQ(1u, templates.size());
ash::DeskTemplate* desk_template = templates.front();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
// Find `browser` window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(browser_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
const auto& data = app_restore_data_iter->second;
// Check the urls are captured correctly in the `desk_template`.
EXPECT_EQ(data->urls.value(), urls);
}
// Tests that incognito browser windows will NOT be captured in the desk
// template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUICaptureIncognitoBrowserTest) {
Browser* incognito_browser = CreateIncognitoBrowser();
chrome::AddTabAt(incognito_browser, GURL(kExampleUrl1), /*index=*/-1,
/*foreground=*/true);
chrome::AddTabAt(incognito_browser, GURL(kExampleUrl2), /*index=*/-1,
/*foreground=*/true);
incognito_browser->window()->Show();
aura::Window* window = incognito_browser->window()->GetNativeWindow();
const int32_t incognito_browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
// Incognito browsers are unsupported so a dialog will popup asking users if
// they are sure. Use a key press to accept the dialog instead of a click as
// dialog buttons think a click generated by the event generator is an
// accidentally click and therefore ignores it.
ClickSaveDeskAsTemplateButton(/*wait_for_ui=*/false);
views::Button* dialog_accept_button =
ash::GetDesksTemplatesDialogAcceptButton();
ASSERT_TRUE(dialog_accept_button);
aura::Window* root_window =
dialog_accept_button->GetWidget()->GetNativeWindow()->GetRootWindow();
ui::test::EventGenerator event_generator(root_window);
event_generator.PressAndReleaseKey(ui::VKEY_RETURN);
std::vector<ash::DeskTemplate*> templates = GetAllEntries();
ASSERT_EQ(1u, templates.size());
ash::DeskTemplate* desk_template = templates.front();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(1u, app_id_to_launch_list.size());
// Find `browser` window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_FALSE(iter == app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(incognito_browser_window_id);
// Created incognito window is NOT in restore list.
EXPECT_TRUE(iter->second.end() == app_restore_data_iter);
}
// Tests that launching a template that contains a system web app works as
// expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchTemplateWithSystemWebApp) {
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(browser()->profile());
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
ASSERT_TRUE(settings_window);
const std::u16string settings_title = settings_window->GetTitle();
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Exit overview and close the settings window. We'll need to verify if it
// reopens later.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
views::Widget* settings_widget =
views::Widget::GetWidgetForNativeWindow(settings_window);
settings_widget->CloseNow();
ASSERT_FALSE(FindBrowserWindow(kSettingsWindowId));
// Enter overview, head over to the desks templates grid and launch the
// template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickZeroStateTemplatesButton();
ClickFirstTemplateItem();
for (auto* browser : *BrowserList::GetInstance()) {
aura::Window* window = browser->window()->GetNativeWindow();
if (window->GetTitle() == settings_title) {
settings_window = window;
break;
}
}
ASSERT_TRUE(settings_window);
EXPECT_EQ(ash::Shell::GetContainer(settings_window->GetRootWindow(),
ash::kShellWindowId_DeskContainerB),
settings_window->parent());
}
// Tests that launching a template that contains a system web app will move the
// existing instance of the system web app to the current desk.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchTemplateWithSWAExisting) {
Profile* profile = browser()->profile();
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(profile);
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
ASSERT_TRUE(settings_window);
EXPECT_EQ(2u, BrowserList::GetInstance()->size());
// Give the settings app a known position.
const gfx::Rect settings_bounds(100, 100, 600, 400);
settings_window->SetBounds(settings_bounds);
// Focus the browser so that the settings window is stacked at the bottom.
browser()->window()->GetNativeWindow()->Focus();
ASSERT_THAT(settings_window->parent()->children(),
ElementsAre(settings_window, _));
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Exit overview and move the settings window to a new place and stack it on
// top so that we can later verify that it has been placed and stacked
// correctly.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
settings_window->SetBounds(gfx::Rect(150, 150, 650, 500));
settings_window->Focus();
// Enter overview, head over to the desks templates grid and launch the
// template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickZeroStateTemplatesButton();
ClickFirstTemplateItem();
// Wait for the tabs to load.
content::RunAllTasksUntilIdle();
// Exit overview.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
ash::DesksController* desks_controller = ash::DesksController::Get();
ASSERT_EQ(1, desks_controller->GetActiveDeskIndex());
// We launch a new browser window, but not a new settings app. Verify that the
// window has been moved to the right place and stacked at the bottom.
EXPECT_EQ(3u, BrowserList::GetInstance()->size());
EXPECT_TRUE(desks_controller->BelongsToActiveDesk(settings_window));
EXPECT_EQ(settings_bounds, settings_window->bounds());
// TODO(crbug.com/1281393): Verify that the element order is correct.
// Tests that there is no clipping on the settings window.
EXPECT_EQ(gfx::Rect(), settings_window->layer()->clip_rect());
}
// Tests that browser windows created from a template have the correct bounds
// and window state.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUIBrowserWindowRestorationTest) {
// Create a new browser and set its bounds.
Browser* browser_1 = CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
const gfx::Rect browser_bounds_1 = gfx::Rect(100, 100, 600, 200);
aura::Window* window_1 = browser_1->window()->GetNativeWindow();
window_1->SetBounds(browser_bounds_1);
// Create a new minimized browser.
Browser* browser_2 = CreateBrowser({GURL(kExampleUrl1)});
const gfx::Rect browser_bounds_2 = gfx::Rect(150, 150, 500, 300);
aura::Window* window_2 = browser_2->window()->GetNativeWindow();
window_2->SetBounds(browser_bounds_2);
EXPECT_EQ(browser_bounds_2, window_2->bounds());
browser_2->window()->Minimize();
// Create a new maximized browser.
Browser* browser_3 = CreateBrowser({GURL(kExampleUrl1)});
browser_3->window()->Maximize();
EXPECT_EQ(browser_bounds_1, window_1->bounds());
EXPECT_EQ(browser_bounds_2, window_2->bounds());
ASSERT_TRUE(browser_2->window()->IsMinimized());
ASSERT_TRUE(browser_3->window()->IsMaximized());
const int32_t browser_window_id_1 =
window_1->GetProperty(app_restore::kWindowIdKey);
const int32_t browser_window_id_2 =
window_2->GetProperty(app_restore::kWindowIdKey);
const int32_t browser_window_id_3 =
browser_3->window()->GetNativeWindow()->GetProperty(
app_restore::kWindowIdKey);
// Capture the active desk, which contains the three browser windows.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
ClickFirstTemplateItem();
// Wait for the tabs to load.
content::RunAllTasksUntilIdle();
// Verify that the browser was launched with the correct bounds.
Browser* new_browser_1 = FindBrowser(browser_window_id_1);
ASSERT_TRUE(new_browser_1);
EXPECT_EQ(browser_bounds_1,
new_browser_1->window()->GetNativeWindow()->bounds());
// Verify that the browser was launched and minimized.
Browser* new_browser_2 = FindBrowser(browser_window_id_2);
ASSERT_TRUE(new_browser_2);
ASSERT_TRUE(new_browser_2->window()->IsMinimized());
EXPECT_EQ(browser_bounds_2,
new_browser_2->window()->GetNativeWindow()->bounds());
// Verify that the browser was launched and maximized.
Browser* new_browser_3 = FindBrowser(browser_window_id_3);
ASSERT_TRUE(new_browser_3);
ASSERT_TRUE(new_browser_3->window()->IsMaximized());
}
// Tests that saving and launching a template that contains a PWA works as
// expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchTemplateWithPWA) {
Browser* pwa_browser =
InstallAndLaunchPWA(GURL(kExampleUrl1), /*launch_in_browser=*/false);
ASSERT_TRUE(pwa_browser->is_type_app());
aura::Window* pwa_window = pwa_browser->window()->GetNativeWindow();
const gfx::Rect pwa_bounds(50, 50, 500, 500);
pwa_window->SetBounds(pwa_bounds);
const int32_t pwa_window_id =
pwa_window->GetProperty(app_restore::kWindowIdKey);
const std::string* app_name =
pwa_window->GetProperty(app_restore::kBrowserAppNameKey);
ASSERT_TRUE(app_name);
// Capture the active desk, which contains the PWA.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
std::vector<ash::DeskTemplate*> templates = GetAllEntries();
ASSERT_EQ(1u, templates.size());
// Find `pwa_browser` window's app restore data.
ash::DeskTemplate* desk_template = templates.front();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(1u, app_id_to_launch_list.size());
ASSERT_TRUE(restore_data->HasAppTypeBrowser());
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(pwa_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
const auto& data = app_restore_data_iter->second;
// Verify window info are correctly captured.
EXPECT_EQ(pwa_bounds, data->current_bounds.value());
ASSERT_TRUE(data->app_type_browser.has_value() &&
data->app_type_browser.value());
EXPECT_EQ(*app_name, *data->app_name);
// Launch the template.
ClickFirstTemplateItem();
// Verify that the PWA was launched correctly.
Browser* new_pwa_browser = FindBrowser(pwa_window_id);
ASSERT_TRUE(new_pwa_browser);
ASSERT_TRUE(new_pwa_browser->is_type_app());
aura::Window* new_browser_window =
new_pwa_browser->window()->GetNativeWindow();
EXPECT_NE(new_browser_window, pwa_window);
EXPECT_EQ(pwa_bounds, new_browser_window->bounds());
const std::string* new_app_name =
new_browser_window->GetProperty(app_restore::kBrowserAppNameKey);
ASSERT_TRUE(new_app_name);
EXPECT_EQ(*app_name, *new_app_name);
}
// Tests that saving and launching a template that contains a PWA in a browser
// window works as expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchTemplateWithPWAInBrowser) {
Browser* pwa_browser =
InstallAndLaunchPWA(GURL(kYoutubeUrl), /*launch_in_browser=*/true);
aura::Window* pwa_window = pwa_browser->window()->GetNativeWindow();
const int32_t pwa_window_id =
pwa_window->GetProperty(app_restore::kWindowIdKey);
// Capture the active desk, which contains the PWA.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
std::vector<ash::DeskTemplate*> templates = GetAllEntries();
ASSERT_EQ(1u, templates.size());
// Test that `pwa_browser` restore data can be found.
ash::DeskTemplate* desk_template = templates.front();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(1u, app_id_to_launch_list.size());
// Test that `pwa_browser`'s restore data is saved under the Chrome browser
// app id extension_misc::kChromeAppId, not Youtube app id
// extension_misc::kYoutubeAppId.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_TRUE(iter != app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(pwa_window_id);
ASSERT_TRUE(app_restore_data_iter != iter->second.end());
iter = app_id_to_launch_list.find(extension_misc::kYoutubeAppId);
EXPECT_TRUE(iter == app_id_to_launch_list.end());
}
// Tests that browsers and SWAs can be captured correctly in the desk template.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUICaptureActiveDeskAsTemplateTest) {
// Change `browser`'s bounds.
const gfx::Rect browser_bounds(800, 200);
aura::Window* window = browser()->window()->GetNativeWindow();
window->SetBounds(browser_bounds);
// Make the window visible on all desks.
window->SetProperty(aura::client::kWindowWorkspaceKey,
aura::client::kWindowWorkspaceVisibleOnAllWorkspaces);
const int32_t browser_window_id =
window->GetProperty(app_restore::kWindowIdKey);
// Create the settings app, which is a system web app.
web_app::AppId settings_app_id =
CreateSettingsSystemWebApp(browser()->profile());
// Change the Settings app's bounds too.
const gfx::Rect settings_app_bounds(100, 100, 800, 300);
aura::Window* settings_window = FindBrowserWindow(kSettingsWindowId);
const int32_t settings_window_id =
settings_window->GetProperty(app_restore::kWindowIdKey);
ASSERT_TRUE(settings_window);
settings_window->SetBounds(settings_app_bounds);
auto* desks_controller = ash::DesksController::Get();
const std::u16string desk_name =
desks_controller->GetDeskName(desks_controller->GetActiveDeskIndex());
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
std::vector<ash::DeskTemplate*> templates = GetAllEntries();
ASSERT_EQ(1u, templates.size());
ash::DeskTemplate* desk_template = templates.front();
// Test the default template's name is the desk's name it was created from.
EXPECT_EQ(desk_name, desk_template->template_name());
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(2u, app_id_to_launch_list.size());
// Find `browser` window's app restore data.
auto iter = app_id_to_launch_list.find(extension_misc::kChromeAppId);
ASSERT_NE(iter, app_id_to_launch_list.end());
auto app_restore_data_iter = iter->second.find(browser_window_id);
ASSERT_NE(iter->second.end(), app_restore_data_iter);
const auto& data = app_restore_data_iter->second;
// Verify window info are correctly captured.
EXPECT_EQ(browser_bounds, data->current_bounds.value());
// `visible_on_all_workspaces` should have been reset even though
// the captured window is visible on all workspaces.
EXPECT_FALSE(data->desk_id.has_value());
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(screen->GetDisplayNearestWindow(window).id(),
data->display_id.value());
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
// We don't capture the window's desk_id as a template will always
// create in a new desk.
EXPECT_FALSE(data->desk_id.has_value());
// Find Setting app's app restore data.
auto iter2 = app_id_to_launch_list.find(settings_app_id);
ASSERT_NE(app_id_to_launch_list.end(), iter2);
auto app_restore_data_iter2 = iter2->second.find(settings_window_id);
ASSERT_NE(iter->second.end(), app_restore_data_iter2);
const auto& data2 = app_restore_data_iter2->second;
EXPECT_EQ(
static_cast<int>(apps::mojom::LaunchContainer::kLaunchContainerWindow),
data2->container.value());
EXPECT_EQ(static_cast<int>(WindowOpenDisposition::NEW_WINDOW),
data2->disposition.value());
// Verify window info are correctly captured.
EXPECT_EQ(settings_app_bounds, data2->current_bounds.value());
EXPECT_FALSE(data2->desk_id.has_value());
EXPECT_EQ(screen->GetDisplayNearestWindow(window).id(),
data->display_id.value());
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
chromeos::ToWindowShowState(data->window_state_type.value()));
EXPECT_FALSE(data2->desk_id.has_value());
}
// Tests that launching a template that contains a chrome app works as expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchTemplateWithChromeApp) {
// Create a chrome app.
const extensions::Extension* extension =
LoadAndLaunchPlatformApp("launch", "Launched");
ASSERT_TRUE(extension);
const std::string extension_id = extension->id();
::full_restore::SaveAppLaunchInfo(
profile()->GetPath(),
std::make_unique<app_restore::AppLaunchInfo>(
extension_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::NEW_WINDOW, display::kDefaultDisplayId,
std::vector<base::FilePath>{}, nullptr));
extensions::AppWindow* app_window = CreateAppWindow(profile(), extension);
ASSERT_TRUE(app_window);
ASSERT_TRUE(GetFirstAppWindowForApp(extension_id));
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Close the chrome app window. We'll need to verify if it reopens later.
views::Widget* app_widget =
views::Widget::GetWidgetForNativeWindow(app_window->GetNativeWindow());
app_widget->CloseNow();
ASSERT_FALSE(GetFirstAppWindowForApp(extension_id));
ash::DesksController* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
// `BrowserAppLauncher::LaunchAppWithParams()` does not launch the chrome app
// in tests, so here we set up a mock app launch handler and just verify a
// `LaunchSystemWebAppOrChromeApp()` call with the associated extension is
// seen.
auto mock_app_launch_handler =
std::make_unique<MockDesksTemplatesAppLaunchHandler>(profile());
MockDesksTemplatesAppLaunchHandler* mock_app_launch_handler_ptr =
mock_app_launch_handler.get();
ScopedDesksTemplatesAppLaunchHandlerSetter scoped_launch_handler(
std::move(mock_app_launch_handler));
EXPECT_CALL(*mock_app_launch_handler_ptr,
LaunchSystemWebAppOrChromeApp(_, extension_id, _));
// Launch the template we saved.
ClickFirstTemplateItem();
}
// Tests that launching the same desk template multiple times creates desks with
// different/incremented names.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUILaunchMultipleDeskTemplates) {
const base::GUID kDeskUuid = base::GUID::GenerateRandomV4();
const std::u16string kDeskName(u"Test Desk Name");
auto* desks_controller = ash::DesksController::Get();
ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
desks_controller->desks()[0]->SetName(kDeskName, true);
// Save a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Helper that launches a template by entering the grid and clicking on the
// lone template item, and then checks the expected name.
bool first_run = true;
auto check_launch_template_desk_name =
[kDeskUuid, &first_run](const std::u16string& desk_name) {
SCOPED_TRACE(desk_name);
// We are already on the templates grid after saving the template, so no
// need to click the templates button the first time we use this helper.
if (!first_run)
ClickExpandedStateTemplatesButton();
ClickFirstTemplateItem();
// Wait for the tabs to load.
content::RunAllTasksUntilIdle();
// A DCHECK related to the desks templates animation fails without this
// wait.
// TODO(sammiequon): Find a proper fix and remove this delay.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(500));
run_loop.Run();
first_run = false;
};
// Launching a desk from the template creates a desk with the same name as the
// template.
desks_controller->desks()[0]->SetName(u"Desk", true);
check_launch_template_desk_name(kDeskName);
// Launch more desks from the template and verify that the newly created desks
// have unique names.
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (1)"));
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (2)"));
// Remove "Test Desk Name (1)", which means the next created desk from
// template will have that name. Then it will skip (2) since it already
// exists, and create the next desk with (3).
RemoveDesk(desks_controller->desks()[2].get());
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (1)"));
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (3)"));
// Same as above, but make sure that deleting the desk with the exact template
// name still functions the same by only filling in whatever name is
// available.
RemoveDesk(desks_controller->desks()[1].get());
check_launch_template_desk_name(kDeskName);
check_launch_template_desk_name(std::u16string(kDeskName).append(u" (4)"));
}
// Tests that the windows and tabs count histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUIDeskTemplateWindowAndTabCountHistogram) {
base::HistogramTester histogram_tester;
// Create the settings app, which is a system web app.
CreateSettingsSystemWebApp(browser()->profile());
CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2)});
CreateBrowser({GURL(kExampleUrl1), GURL(kExampleUrl2), GURL(kExampleUrl3)});
// Save a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
constexpr char kWindowCountHistogramName[] = "Ash.DeskTemplate.WindowCount";
constexpr char kTabCountHistogramName[] = "Ash.DeskTemplate.TabCount";
constexpr char kWindowAndTabCountHistogramName[] =
"Ash.DeskTemplate.WindowAndTabCount";
// NOTE: there is an existing browser with 1 tab created by BrowserMain().
histogram_tester.ExpectBucketCount(kWindowCountHistogramName, 4, 1);
histogram_tester.ExpectBucketCount(kTabCountHistogramName, 6, 1);
histogram_tester.ExpectBucketCount(kWindowAndTabCountHistogramName, 7, 1);
}
// Tests that the launch from template histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUIDeskTemplateLaunchFromTemplateHistogram) {
base::HistogramTester histogram_tester;
// Create a new browser.
CreateBrowser({});
// Save a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
const int launches = 5;
for (int i = 0; i < launches; i++) {
ClickFirstTemplateItem();
// A DCHECK related to the desks templates animation fails without this
// wait.
// TODO(sammiequon): Find a proper fix and remove this delay.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(500));
run_loop.Run();
ClickExpandedStateTemplatesButton();
}
// TODO(crbug.com/1287649): This histogram is double counted when a template
// is launched from the UI.
constexpr char kLaunchFromTemplateHistogramName[] =
"Ash.DeskTemplate.LaunchFromTemplate";
histogram_tester.ExpectTotalCount(kLaunchFromTemplateHistogramName,
2 * launches);
}
// Tests that the template count histogram is recorded properly.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUIDeskTemplateUserTemplateCountHistogram) {
base::HistogramTester histogram_tester;
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
// Save 3 templates.
const int saves = 3;
for (int i = 0; i < saves; i++) {
ClickSaveDeskAsTemplateButton();
// Exit and renenter overview to save the next template. Once we are viewing
// the grid we can't go back to regular overview unless we exit overview or
// delete all the templates.
if (i != saves - 1) {
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
}
}
views::Button* delete_button = ash::GetTemplateItemDeleteButton(/*index=*/0);
ClickButton(delete_button);
// Confirm deleting a template. Use a key press to accept the dialog instead
// of a click as dialog buttons think a click generated by the event generator
// is an accidentally click and therefore ignores it.
views::Button* dialog_accept_button =
ash::GetDesksTemplatesDialogAcceptButton();
ASSERT_TRUE(dialog_accept_button);
aura::Window* root_window =
dialog_accept_button->GetWidget()->GetNativeWindow()->GetRootWindow();
ui::test::EventGenerator event_generator(root_window);
event_generator.PressAndReleaseKey(ui::VKEY_RETURN);
// Wait for the model to update.
ash::WaitForDesksTemplatesUI();
// Save one more template.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Verify that all template saves and deletes are captured by the histogram.
constexpr char kUserTemplateCountHistogramName[] =
"Ash.DeskTemplate.UserTemplateCount";
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 1, 1);
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 2, 2);
histogram_tester.ExpectBucketCount(kUserTemplateCountHistogramName, 3, 2);
}
// Tests that browser session restore isn't triggered when we launch a template
// that contains a browser window.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientTest,
NativeUIPreventBrowserSessionRestoreTest) {
// Do not exit from test or delete the Profile* when last browser is closed.
ScopedKeepAlive keep_alive(KeepAliveOrigin::BROWSER,
KeepAliveRestartOption::DISABLED);
ScopedProfileKeepAlive profile_keep_alive(
browser()->profile(), ProfileKeepAliveOrigin::kBrowserWindow);
// Enable session service.
SessionStartupPref pref(SessionStartupPref::LAST);
Profile* profile = browser()->profile();
SessionStartupPref::SetStartupPref(profile, pref);
const int expected_tab_count = 2;
chrome::AddTabAt(browser(), GURL(kExampleUrl2), /*index=*/-1,
/*foreground=*/true);
EXPECT_EQ(expected_tab_count, browser()->tab_strip_model()->count());
const int32_t browser_window_id =
browser()->window()->GetNativeWindow()->GetProperty(
app_restore::kWindowIdKey);
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
// Exit overview, close the browser and verify that all browser windows are
// closed.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
CloseBrowserSynchronously(browser());
EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
// Reenter overview and launch the template we saved.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ash::WaitForDesksTemplatesUI();
ClickZeroStateTemplatesButton();
ClickFirstTemplateItem();
// Verify that the browser was launched with the correct number of tabs, and
// that browser session restore did not restore any windows/tabs.
Browser* new_browser = FindBrowser(browser_window_id);
ASSERT_TRUE(new_browser);
EXPECT_EQ(expected_tab_count, GetURLsForBrowserWindow(new_browser).size());
EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
}
class DesksTemplatesClientArcTest : public InProcessBrowserTest {
public:
DesksTemplatesClientArcTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{full_restore::features::kFullRestore,
ash::features::kDesksTemplates},
/*disabled_features=*/{ash::features::kDeskTemplateSync});
}
DesksTemplatesClientArcTest(const DesksTemplatesClientArcTest&) = delete;
DesksTemplatesClientArcTest& operator=(const DesksTemplatesClientArcTest&) =
delete;
~DesksTemplatesClientArcTest() override = default;
ash::AppRestoreArcTestHelper* arc_helper() { return &arc_helper_; }
// InProcessBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
arc_helper_.SetUpCommandLine(command_line);
InProcessBrowserTest::SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
arc_helper_.SetUpInProcessBrowserTestFixture();
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
arc_helper_.SetUpOnMainThread(browser()->profile());
InProcessBrowserTest::SetUpOnMainThread();
}
private:
ash::AppRestoreArcTestHelper arc_helper_;
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that launching a template that contains an ARC app works as expected.
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientArcTest,
NativeUILaunchTemplateWithArcApp) {
auto* desk_model = DesksTemplatesClient::Get()->GetDeskModel();
ASSERT_EQ(0, desk_model->GetEntryCount());
constexpr char kTestAppPackage[] = "test.arc.app.package";
arc_helper()->InstallTestApps(kTestAppPackage, /*multi_app=*/false);
const std::string app_id = ash::GetTestApp1Id(kTestAppPackage);
int32_t session_id1 =
full_restore::FullRestoreSaveHandler::GetInstance()->GetArcSessionId();
// Create the window for app1. The task id needs to match the
// `window_app_id` arg of `CreateExoWindow`.
const int32_t kTaskId1 = 100;
views::Widget* widget = ash::CreateExoWindow("org.chromium.arc.100");
widget->SetBounds(gfx::Rect(500, 500));
full_restore::SaveAppLaunchInfo(browser()->profile()->GetPath(),
std::make_unique<app_restore::AppLaunchInfo>(
app_id, ui::EventFlags::EF_NONE,
session_id1, display::kDefaultDisplayId));
// Simulate creating the task.
arc_helper()->CreateTask(app_id, kTaskId1, session_id1);
// Enter overview and save the current desk as a template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickSaveDeskAsTemplateButton();
ASSERT_EQ(1, desk_model->GetEntryCount());
// Exit overview and close the Arc window. We'll need to verify if it
// reopens later.
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
widget->CloseNow();
arc_helper()->GetAppHost()->OnTaskDestroyed(kTaskId1);
// Enter overview, head over to the desks templates grid and launch the
// template.
ash::ToggleOverview();
ash::WaitForOverviewEnterAnimation();
ClickZeroStateTemplatesButton();
ClickFirstTemplateItem();
ash::ToggleOverview();
ash::WaitForOverviewExitAnimation();
// Create the window to simulate launching the ARC app.
const int32_t kTaskId2 = 200;
auto* widget1 = ash::CreateExoWindow("org.chromium.arc.200");
auto* window1 = widget1->GetNativeWindow();
arc_helper()->CreateTask(app_id, kTaskId2, session_id1);
// Tests that the ARC app is launched on desk 2.
EXPECT_EQ(ash::Shell::GetContainer(window1->GetRootWindow(),
ash::kShellWindowId_DeskContainerB),
window1->parent());
widget1->CloseNow();
arc_helper()->GetAppHost()->OnTaskDestroyed(kTaskId2);
arc_helper()->StopInstance();
}
class DesksTemplatesClientMultiProfileTest : public ash::LoginManagerTest {
public:
DesksTemplatesClientMultiProfileTest() : ash::LoginManagerTest() {
login_mixin_.AppendRegularUsers(2);
account_id1_ = login_mixin_.users()[0].account_id;
account_id2_ = login_mixin_.users()[1].account_id;
// This feature depends on full restore feature, so need to enable it.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{full_restore::features::kFullRestore},
/*disabled_features=*/{ash::features::kDeskTemplateSync});
}
~DesksTemplatesClientMultiProfileTest() override = default;
void SetUpOnMainThread() override {
ash::LoginManagerTest::SetUpOnMainThread();
LoginUser(account_id1_);
::full_restore::SetActiveProfilePath(
ash::ProfileHelper::Get()
->GetProfileByAccountId(account_id1_)
->GetPath());
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
ash::LoginManagerMixin login_mixin_{&mixin_host_};
AccountId account_id1_;
AccountId account_id2_;
};
IN_PROC_BROWSER_TEST_F(DesksTemplatesClientMultiProfileTest, MultiProfileTest) {
CreateBrowser(ash::ProfileHelper::Get()->GetProfileByAccountId(account_id1_));
// Capture the active desk, which contains the browser windows.
std::unique_ptr<ash::DeskTemplate> desk_template =
CaptureActiveDeskAndSaveTemplate();
const app_restore::RestoreData* restore_data =
desk_template->desk_restore_data();
const auto& app_id_to_launch_list = restore_data->app_id_to_launch_list();
EXPECT_EQ(app_id_to_launch_list.size(), 1u);
auto get_templates_size = []() {
base::RunLoop run_loop;
int templates_num = 0;
DesksTemplatesClient::Get()->GetDeskTemplates(base::BindLambdaForTesting(
[&](const std::vector<ash::DeskTemplate*>& desk_templates,
std::string error_string) {
templates_num = desk_templates.size();
run_loop.Quit();
}));
run_loop.Run();
return templates_num;
};
EXPECT_EQ(get_templates_size(), 1);
// Now switch to |account_id2_|. Test that the captured desk template can't
// be accessed from |account_id2_|.
ash::UserAddingScreen::Get()->Start();
AddUser(account_id2_);
EXPECT_EQ(get_templates_size(), 0);
}