#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include <map>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <tuple>
#include <vector>
#include "base/base_paths.h"
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
#include "base/test/bind.h"
#include "base/threading/thread_restrictions.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
#include "chrome/browser/web_applications/os_integration/web_app_file_handler_registration.h"
#include "chrome/browser/web_applications/test/fake_environment.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "components/webapps/common/web_app_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#if BUILDFLAG(IS_LINUX)
#include "base/nix/xdg_util.h"
#endif
#if BUILDFLAG(IS_MAC)
#include <ImageIO/ImageIO.h>
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/web_applications/os_integration/mac/app_shim_registry.h"
#include "net/base/filename_util.h"
#import "skia/ext/skia_utils_mac.h"
#endif
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <shellapi.h>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/shortcut.h"
#include "base/win/windows_types.h"
#include "chrome/browser/web_applications/os_integration/web_app_handler_registration_utils_win.h"
#include "chrome/browser/web_applications/os_integration/web_app_uninstallation_via_os_settings_registration.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/win/jumplist_updater.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/shell_util.h"
#include "third_party/re2/src/re2/re2.h"
#include "ui/gfx/icon_util.h"
#endif
namespace web_app {
namespace {
#if BUILDFLAG(IS_WIN)
constexpr wchar_t kUninstallRegistryKey[] =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
base::FilePath GetShortcutProfile(base::FilePath shortcut_path) {
base::FilePath shortcut_profile;
std::wstring cmd_line_string;
if (base::win::ResolveShortcut(shortcut_path, nullptr, &cmd_line_string)) {
base::CommandLine shortcut_cmd_line =
base::CommandLine::FromString(L"program " + cmd_line_string);
shortcut_profile =
shortcut_cmd_line.GetSwitchValuePath(switches::kProfileDirectory);
}
return shortcut_profile;
}
std::vector<std::wstring> GetFileExtensionsForProgId(
const std::wstring& file_handler_prog_id) {
const std::wstring prog_id_path =
base::StrCat({ShellUtil::kRegClasses, L"\\", file_handler_prog_id});
base::win::RegKey file_extensions_key(HKEY_CURRENT_USER, prog_id_path.c_str(),
KEY_QUERY_VALUE);
std::wstring handled_file_extensions;
LONG result = file_extensions_key.ReadValue(L"FileExtensions",
&handled_file_extensions);
CHECK_EQ(result, ERROR_SUCCESS);
return base::SplitString(handled_file_extensions, std::wstring(L";"),
base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
SkColor IconManagerReadIconTopLeftColorForSize(WebAppIconManager& icon_manager,
const webapps::AppId& app_id,
SquareSizePx size_px) { … }
#endif
}
OsIntegrationTestOverrideBlockingRegistration::
OsIntegrationTestOverrideBlockingRegistration() { … }
OsIntegrationTestOverrideBlockingRegistration::
~OsIntegrationTestOverrideBlockingRegistration() { … }
OsIntegrationTestOverrideImpl&
OsIntegrationTestOverrideBlockingRegistration::test_override() const { … }
scoped_refptr<OsIntegrationTestOverrideImpl>
OsIntegrationTestOverrideImpl::Get() { … }
std::unique_ptr<OsIntegrationTestOverrideImpl::BlockingRegistration>
OsIntegrationTestOverrideImpl::OverrideForTesting() { … }
bool OsIntegrationTestOverrideImpl::SimulateDeleteShortcutsByUser(
Profile* profile,
const webapps::AppId& app_id,
const std::string& app_name) { … }
#if BUILDFLAG(IS_MAC)
bool OsIntegrationTestOverrideImpl::DeleteChromeAppsDir() {
if (chrome_apps_folder_.IsValid()) {
bool success = chrome_apps_folder_.Delete();
if (!success) {
success = chrome_apps_folder_.Delete();
}
return success;
} else {
return false;
}
}
#endif
#if BUILDFLAG(IS_WIN)
bool OsIntegrationTestOverrideImpl::DeleteDesktopDirOnWin() {
if (desktop_.IsValid()) {
return desktop_.Delete();
} else {
return false;
}
}
bool OsIntegrationTestOverrideImpl::DeleteApplicationMenuDirOnWin() {
if (application_menu_.IsValid()) {
return application_menu_.Delete();
} else {
return false;
}
}
#endif
#if BUILDFLAG(IS_LINUX)
bool OsIntegrationTestOverrideImpl::DeleteDesktopDirOnLinux() { … }
#endif
bool OsIntegrationTestOverrideImpl::IsRunOnOsLoginEnabled(
Profile* profile,
const webapps::AppId& app_id,
const std::string& app_name) { … }
bool OsIntegrationTestOverrideImpl::IsFileExtensionHandled(
Profile* profile,
const webapps::AppId& app_id,
std::string app_name,
std::string file_extension) { … }
std::optional<SkColor>
OsIntegrationTestOverrideImpl::GetShortcutIconTopLeftColor(
Profile* profile,
base::FilePath shortcut_dir,
const webapps::AppId& app_id,
const std::string& app_name,
SquareSizePx size_px) { … }
base::FilePath OsIntegrationTestOverrideImpl::GetShortcutPath(
Profile* profile,
base::FilePath shortcut_dir,
const webapps::AppId& app_id,
const std::string& app_name) { … }
bool OsIntegrationTestOverrideImpl::IsShortcutCreated(
Profile* profile,
const webapps::AppId& app_id,
const std::string& app_name) { … }
bool OsIntegrationTestOverrideImpl::AreShortcutsMenuRegistered() { … }
#if BUILDFLAG(IS_WIN)
std::vector<SkColor>
OsIntegrationTestOverrideImpl::GetIconColorsForShortcutsMenu(
const std::wstring& app_user_model_id) {
CHECK(IsShortcutsMenuRegisteredForApp(app_user_model_id));
std::vector<SkColor> icon_colors;
for (auto& shell_link_item : jump_list_entry_map_[app_user_model_id]) {
icon_colors.emplace_back(
ReadColorFromShortcutMenuIcoFile(shell_link_item->icon_path()));
}
return icon_colors;
}
int OsIntegrationTestOverrideImpl::GetCountOfShortcutIconsCreated(
const std::wstring& app_user_model_id) {
CHECK(IsShortcutsMenuRegisteredForApp(app_user_model_id));
return jump_list_entry_map_[app_user_model_id].size();
}
bool OsIntegrationTestOverrideImpl::IsShortcutsMenuRegisteredForApp(
const std::wstring& app_user_model_id) {
return base::Contains(jump_list_entry_map_, app_user_model_id);
}
base::expected<bool, std::string>
OsIntegrationTestOverrideImpl::IsUninstallRegisteredWithOs(
const webapps::AppId& app_id,
const std::string& app_name,
Profile* profile) {
base::win::RegKey uninstall_reg_key;
LONG result = uninstall_reg_key.Open(HKEY_CURRENT_USER, kUninstallRegistryKey,
KEY_READ);
if (result == ERROR_FILE_NOT_FOUND) {
return base::unexpected(
"Cannot find the uninstall registry key. If a testing hive is being "
"used, then this key needs to be created there on initialization.");
}
if (result != ERROR_SUCCESS) {
return base::unexpected(
base::StringPrintf("Cannot open the registry key: %ld", result));
}
const std::wstring key =
GetUninstallStringKeyForTesting(profile->GetPath(), app_id);
base::win::RegKey uninstall_reg_entry_key;
result = uninstall_reg_entry_key.Open(uninstall_reg_key.Handle(), key.c_str(),
KEY_READ);
if (result == ERROR_FILE_NOT_FOUND) {
return base::ok(false);
}
if (result != ERROR_SUCCESS) {
return base::unexpected(
base::StringPrintf("Error opening uninstall key for app: %ld", result));
}
std::wstring display_icon_path;
std::wstring display_name;
std::wstring display_version;
std::wstring application_version;
std::wstring publisher;
std::wstring uninstall_string;
DWORD no_repair;
DWORD no_modify;
bool read_success = true;
read_success &= uninstall_reg_entry_key.ReadValue(
L"DisplayIcon", &display_icon_path) == ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValue(
L"DisplayName", &display_name) == ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValue(
L"DisplayVersion", &display_version) == ERROR_SUCCESS;
read_success &=
uninstall_reg_entry_key.ReadValue(L"ApplicationVersion",
&application_version) == ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValue(L"Publisher", &publisher) ==
ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValue(
L"UninstallString", &uninstall_string) == ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValueDW(
L"NoRepair", &no_repair) == ERROR_SUCCESS;
read_success &= uninstall_reg_entry_key.ReadValueDW(
L"NoModify", &no_modify) == ERROR_SUCCESS;
if (!read_success) {
return base::unexpected("Error reading registry values");
}
if (display_version != L"1.0" || application_version != L"1.0" ||
no_repair != 1 || no_modify != 1 ||
publisher != install_static::GetChromeInstallSubDirectory()) {
return base::unexpected("Incorrect static registry data.");
}
base::FilePath web_app_icon_dir = GetOsIntegrationResourcesDirectoryForApp(
profile->GetPath(), app_id, GURL());
base::FilePath expected_icon_path =
internals::GetIconFilePath(web_app_icon_dir, base::UTF8ToUTF16(app_name));
if (expected_icon_path.value() != display_icon_path) {
return base::unexpected(base::StrCat(
{"Invalid icon path ", base::WideToUTF8(display_icon_path),
", expected ", base::WideToUTF8(expected_icon_path.value())}));
}
if (base::UTF8ToWide(app_name) != display_name) {
return base::unexpected(
base::StrCat({"Invalid display name ", base::WideToUTF8(display_name),
", expected ", app_name}));
}
std::wstring expected_uninstall_substr =
base::StrCat({L"--uninstall-app-id=", base::UTF8ToWide(app_id)});
if (!base::Contains(uninstall_string, expected_uninstall_substr)) {
return base::unexpected(base::StrCat({"Could not find uninstall flag: ",
base::WideToUTF8(uninstall_string)}));
}
return true;
}
#endif
const OsIntegrationTestOverrideImpl::AppProtocolList&
OsIntegrationTestOverrideImpl::protocol_scheme_registrations() { … }
OsIntegrationTestOverrideImpl*
OsIntegrationTestOverrideImpl::AsOsIntegrationTestOverrideImpl() { … }
#if BUILDFLAG(IS_WIN)
void OsIntegrationTestOverrideImpl::AddShortcutsMenuJumpListEntryForApp(
const std::wstring& app_user_model_id,
const std::vector<scoped_refptr<ShellLinkItem>>& shell_link_items) {
jump_list_entry_map_[app_user_model_id] = shell_link_items;
shortcut_menu_apps_registered_.emplace(app_user_model_id);
}
void OsIntegrationTestOverrideImpl::DeleteShortcutsMenuJumpListEntryForApp(
const std::wstring& app_user_model_id) {
jump_list_entry_map_.erase(app_user_model_id);
shortcut_menu_apps_registered_.erase(app_user_model_id);
}
base::FilePath OsIntegrationTestOverrideImpl::desktop() {
return desktop_.GetPath();
}
base::FilePath OsIntegrationTestOverrideImpl::application_menu() {
return application_menu_.GetPath().Append(
InstallUtil::GetChromeAppsShortcutDirName());
}
base::FilePath OsIntegrationTestOverrideImpl::quick_launch() {
return quick_launch_.GetPath();
}
base::FilePath OsIntegrationTestOverrideImpl::startup() {
return startup_.GetPath();
}
#endif
#if BUILDFLAG(IS_MAC)
bool OsIntegrationTestOverrideImpl::IsChromeAppsValid() {
return chrome_apps_folder_.IsValid();
}
base::FilePath OsIntegrationTestOverrideImpl::chrome_apps_folder() {
return chrome_apps_folder_.GetPath();
}
void OsIntegrationTestOverrideImpl::EnableOrDisablePathOnLogin(
const base::FilePath& file_path,
bool enable_on_login) {
startup_enabled_[file_path] = enable_on_login;
}
#endif
#if BUILDFLAG(IS_LINUX)
base::FilePath OsIntegrationTestOverrideImpl::desktop() { … }
base::FilePath OsIntegrationTestOverrideImpl::startup() { … }
base::FilePath OsIntegrationTestOverrideImpl::applications() { … }
base::FilePath OsIntegrationTestOverrideImpl::xdg_data_home_dir() { … }
base::Environment* OsIntegrationTestOverrideImpl::environment() { … }
#endif
void OsIntegrationTestOverrideImpl::RegisterProtocolSchemes(
const webapps::AppId& app_id,
std::vector<std::string> protocols) { … }
OsIntegrationTestOverrideImpl::OsIntegrationTestOverrideImpl(
const base::FilePath& base_path) { … }
OsIntegrationTestOverrideImpl::~OsIntegrationTestOverrideImpl() { … }
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
SkColor OsIntegrationTestOverrideImpl::GetIconTopLeftColorFromShortcutFile(
const base::FilePath& shortcut_path) {
CHECK(base::PathExists(shortcut_path));
#if BUILDFLAG(IS_MAC)
base::FilePath icon_path =
shortcut_path.AppendASCII("Contents/Resources/app.icns");
base::apple::ScopedCFTypeRef<CFDictionaryRef> empty_dict(
CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr));
base::apple::ScopedCFTypeRef<CFURLRef> url =
base::apple::FilePathToCFURL(icon_path);
base::apple::ScopedCFTypeRef<CGImageSourceRef> source(
CGImageSourceCreateWithURL(url.get(), nullptr));
if (!source) {
return 0;
}
base::apple::ScopedCFTypeRef<CGImageRef> cg_image(
CGImageSourceCreateImageAtIndex(source.get(), 0, empty_dict.get()));
if (!cg_image) {
return 0;
}
SkBitmap bitmap = skia::CGImageToSkBitmap(cg_image.get());
if (bitmap.empty()) {
return 0;
}
return bitmap.getColor(0, 0);
#elif BUILDFLAG(IS_WIN)
SHFILEINFO file_info = {0};
if (SHGetFileInfo(shortcut_path.value().c_str(), FILE_ATTRIBUTE_NORMAL,
&file_info, sizeof(file_info),
SHGFI_ICON | 0 | SHGFI_USEFILEATTRIBUTES)) {
const SkBitmap bitmap = IconUtil::CreateSkBitmapFromHICON(file_info.hIcon);
if (bitmap.empty()) {
return 0;
}
return bitmap.getColor(0, 0);
} else {
return 0;
}
#endif
}
#endif
#if BUILDFLAG(IS_WIN)
SkColor OsIntegrationTestOverrideImpl::ReadColorFromShortcutMenuIcoFile(
const base::FilePath& file_path) {
HICON icon = static_cast<HICON>(
LoadImage(NULL, file_path.value().c_str(), IMAGE_ICON, 32, 32,
LR_LOADTRANSPARENT | LR_LOADFROMFILE));
base::win::ScopedHICON scoped_icon(icon);
SkBitmap output_image =
IconUtil::CreateSkBitmapFromHICON(scoped_icon.get(), gfx::Size(32, 32));
SkColor color = output_image.getColor(output_image.dimensions().width() / 2,
output_image.dimensions().height() / 2);
return color;
}
#endif
}