blob: f9fdcffa0abcbe1422ca952c0c2883082ffd2b4c [file] [log] [blame]
// Copyright 2018 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/web_applications/preinstalled_web_app_manager.h"
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/user_type_filter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/extension_status_utils.h"
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/externally_managed_app_manager.h"
#include "chrome/browser/web_applications/file_utils_wrapper.h"
#include "chrome/browser/web_applications/preinstalled_app_install_features.h"
#include "chrome/browser/web_applications/preinstalled_web_app_utils.h"
#include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/touchscreen_device.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "components/arc/arc_util.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace web_app {
namespace {
#if defined(OS_CHROMEOS)
// The sub-directory of the extensions directory in which to scan for external
// web apps (as opposed to external extensions or external ARC apps).
const base::FilePath::CharType kWebAppsSubDirectory[] =
FILE_PATH_LITERAL("web_apps");
#endif // defined(OS_CHROMEOS)
bool g_skip_startup_for_testing_ = false;
bool g_bypass_offline_manifest_requirement_for_testing_ = false;
const base::FilePath* g_config_dir_for_testing = nullptr;
const std::vector<base::Value>* g_configs_for_testing = nullptr;
FileUtilsWrapper* g_file_utils_for_testing = nullptr;
struct LoadedConfig {
base::Value contents;
base::FilePath file;
};
struct LoadedConfigs {
std::vector<LoadedConfig> configs;
std::vector<std::string> errors;
};
LoadedConfigs LoadConfigsBlocking(std::vector<base::FilePath> config_dirs) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
LoadedConfigs result;
base::FilePath::StringType extension(FILE_PATH_LITERAL(".json"));
for (auto config_dir : config_dirs) {
base::FileEnumerator json_files(config_dir,
false, // Recursive.
base::FileEnumerator::FILES);
for (base::FilePath file = json_files.Next(); !file.empty();
file = json_files.Next()) {
if (!file.MatchesExtension(extension)) {
continue;
}
JSONFileValueDeserializer deserializer(file);
std::string error_msg;
std::unique_ptr<base::Value> app_config =
deserializer.Deserialize(nullptr, &error_msg);
if (!app_config) {
result.errors.push_back(base::StrCat(
{file.AsUTF8Unsafe(), " was not valid JSON: ", error_msg}));
VLOG(1) << result.errors.back();
continue;
}
result.configs.push_back(
{.contents = std::move(*app_config), .file = file});
}
}
return result;
}
struct ParsedConfigs {
std::vector<ExternalInstallOptions> options_list;
std::vector<std::string> errors;
};
ParsedConfigs ParseConfigsBlocking(LoadedConfigs loaded_configs) {
ParsedConfigs result;
result.errors = std::move(loaded_configs.errors);
scoped_refptr<FileUtilsWrapper> file_utils =
g_file_utils_for_testing ? base::WrapRefCounted(g_file_utils_for_testing)
: base::MakeRefCounted<FileUtilsWrapper>();
for (const LoadedConfig& loaded_config : loaded_configs.configs) {
OptionsOrError parse_result =
ParseConfig(*file_utils, loaded_config.file.DirName(),
loaded_config.file, loaded_config.contents);
if (ExternalInstallOptions* options =
absl::get_if<ExternalInstallOptions>(&parse_result)) {
result.options_list.push_back(std::move(*options));
} else {
result.errors.push_back(std::move(absl::get<std::string>(parse_result)));
VLOG(1) << result.errors.back();
}
}
return result;
}
absl::optional<std::string> GetDisableReason(
const ExternalInstallOptions& options,
Profile* profile,
WebAppRegistrar* registrar,
bool preinstalled_apps_enabled_in_prefs,
bool is_new_user,
const std::string& user_type) {
DCHECK(registrar);
if (!preinstalled_apps_enabled_in_prefs) {
return options.install_url.spec() +
" disabled by preinstalled_apps pref setting.";
}
// Remove if not applicable to current user type.
DCHECK_GT(options.user_type_allowlist.size(), 0u);
if (!base::Contains(options.user_type_allowlist, user_type)) {
return options.install_url.spec() + " disabled for user type: " + user_type;
}
// Remove if gated on a disabled feature.
if (options.gate_on_feature && !IsPreinstalledAppInstallFeatureEnabled(
*options.gate_on_feature, *profile)) {
return options.install_url.spec() +
" disabled because feature is disabled: " + *options.gate_on_feature;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Remove if ARC is supported and app should be disabled.
if (options.disable_if_arc_supported && arc::IsArcAvailable()) {
return options.install_url.spec() + " disabled because ARC is available.";
}
// Remove if device is tablet and app should be disabled.
if (options.disable_if_tablet_form_factor &&
chromeos::switches::IsTabletFormFactor()) {
return options.install_url.spec() + " disabled because device is tablet.";
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Remove if only for new users, user isn't new and app was not
// installed previously.
if (options.only_for_new_users && !is_new_user) {
bool was_previously_installed =
ExternallyInstalledWebAppPrefs(profile->GetPrefs())
.LookupAppId(options.install_url)
.has_value();
if (!was_previously_installed) {
return options.install_url.spec() +
" disabled because user was not new when config was added.";
}
}
// Remove if was not previously preinstalled.
if (options.only_if_previously_preinstalled) {
absl::optional<AppId> app_id =
ExternallyInstalledWebAppPrefs(profile->GetPrefs())
.LookupAppId(options.install_url);
bool was_previously_preinstalled = false;
if (app_id.has_value()) {
const WebApp* web_app = registrar->GetAppById(app_id.value());
if (web_app && web_app->IsPreinstalledApp())
was_previously_preinstalled = true;
}
if (!was_previously_preinstalled) {
return options.install_url.spec() +
" disabled because was not previously preinstalled.";
}
}
// Remove if any apps to replace are blocked or force installed by admin
// policy.
for (const AppId& app_id : options.uninstall_and_replace) {
if (extensions::IsExtensionBlockedByPolicy(profile, app_id)) {
return options.install_url.spec() +
" disabled due to admin policy blocking replacement "
"Extension.";
}
std::u16string reason;
if (extensions::IsExtensionForceInstalled(profile, app_id, &reason)) {
return options.install_url.spec() +
" disabled due to admin policy force installing replacement "
"Extension: " +
base::UTF16ToUTF8(reason);
}
}
// Keep if any apps to replace are installed.
for (const AppId& app_id : options.uninstall_and_replace) {
if (extensions::IsExtensionInstalled(profile, app_id)) {
return absl::nullopt;
}
}
#if !defined(OS_CHROMEOS)
// Remove if it's a default app and the apps to replace are not installed and
// default extension apps are not performing new installation.
if (options.gate_on_feature && !options.uninstall_and_replace.empty() &&
!extensions::DidPreinstalledAppsPerformNewInstallation(profile)) {
for (const AppId& app_id : options.uninstall_and_replace) {
// First time migration and the app to replace is uninstalled as it passed
// the last code block. Save the information that the app was
// uninstalled by user.
if (!WasMigrationRun(profile, *options.gate_on_feature)) {
if (extensions::IsPreinstalledAppId(app_id)) {
MarkPreinstalledAppAsUninstalled(profile, app_id);
return options.install_url.spec() +
"disabled because it's default app and apps to replace were "
"uninstalled.";
}
} else {
// Not first time migration, can't determine if the app to replace is
// uninstalled by user as the migration is already run, use the pref
// saved in first migration.
if (WasPreinstalledAppUninstalled(profile, app_id)) {
return options.install_url.spec() +
"disabled because it's default app and apps to replace were "
"uninstalled.";
}
}
}
}
#endif // !defined(OS_CHROMEOS)
// Remove if any apps to replace were previously uninstalled.
for (const AppId& app_id : options.uninstall_and_replace) {
if (extensions::IsExternalExtensionUninstalled(profile, app_id)) {
return options.install_url.spec() +
" disabled because apps to replace were uninstalled.";
}
}
// Only install if device has a built-in touch screen with stylus support.
if (options.disable_if_touchscreen_with_stylus_not_supported) {
DCHECK(ui::DeviceDataManager::GetInstance()->AreDeviceListsComplete());
bool have_touchscreen_with_stylus = false;
for (const ui::TouchscreenDevice& device :
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
if (device.has_stylus &&
device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
have_touchscreen_with_stylus = true;
break;
}
}
if (!have_touchscreen_with_stylus) {
return options.install_url.spec() +
" disabled because the device does not have a built-in "
"touchscreen with stylus support.";
}
}
return absl::nullopt;
}
std::string GetConfigDirectoryFromCommandLine() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kPreinstalledWebAppsDir);
}
std::string GetExtraConfigSubdirectory() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
chromeos::switches::kExtraWebAppsDir);
#else
return std::string();
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
} // namespace
const char* PreinstalledWebAppManager::kHistogramEnabledCount =
"WebApp.Preinstalled.EnabledCount";
const char* PreinstalledWebAppManager::kHistogramDisabledCount =
"WebApp.Preinstalled.DisabledCount";
const char* PreinstalledWebAppManager::kHistogramConfigErrorCount =
"WebApp.Preinstalled.ConfigErrorCount";
const char* PreinstalledWebAppManager::kHistogramInstallResult =
"Webapp.InstallResult.Default";
const char* PreinstalledWebAppManager::kHistogramUninstallAndReplaceCount =
"WebApp.Preinstalled.UninstallAndReplaceCount";
void PreinstalledWebAppManager::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterStringPref(prefs::kWebAppsLastPreinstallSynchronizeVersion,
"");
registry->RegisterListPref(prefs::kWebAppsMigratedPreinstalledApps);
registry->RegisterListPref(prefs::kWebAppsDidMigrateDefaultChromeApps);
registry->RegisterListPref(prefs::kWebAppsUninstalledDefaultChromeApps);
}
void PreinstalledWebAppManager::SkipStartupForTesting() {
g_skip_startup_for_testing_ = true;
}
void PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting() {
g_bypass_offline_manifest_requirement_for_testing_ = true;
}
void PreinstalledWebAppManager::SetConfigDirForTesting(
const base::FilePath* config_dir) {
g_config_dir_for_testing = config_dir;
}
void PreinstalledWebAppManager::SetConfigsForTesting(
const std::vector<base::Value>* configs) {
g_configs_for_testing = configs;
}
void PreinstalledWebAppManager::SetFileUtilsForTesting(
FileUtilsWrapper* file_utils) {
g_file_utils_for_testing = file_utils;
}
PreinstalledWebAppManager::PreinstalledWebAppManager(Profile* profile)
: profile_(profile) {
if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) {
debug_info_ = std::make_unique<DebugInfo>();
}
}
PreinstalledWebAppManager::~PreinstalledWebAppManager() = default;
void PreinstalledWebAppManager::SetSubsystems(
WebAppRegistrar* registrar,
ExternallyManagedAppManager* externally_managed_app_manager) {
registrar_ = registrar;
externally_managed_app_manager_ = externally_managed_app_manager;
}
void PreinstalledWebAppManager::Start() {
if (!g_skip_startup_for_testing_) {
LoadAndSynchronize(
base::BindOnce(&PreinstalledWebAppManager::OnStartUpTaskCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
}
void PreinstalledWebAppManager::LoadForTesting(ConsumeInstallOptions callback) {
Load(std::move(callback));
}
void PreinstalledWebAppManager::LoadAndSynchronizeForTesting(
SynchronizeCallback callback) {
LoadAndSynchronize(std::move(callback));
}
void PreinstalledWebAppManager::LoadAndSynchronize(
SynchronizeCallback callback) {
// Make sure ExtensionSystem is ready to know if default apps new installation
// will be performed.
extensions::OnExtensionSystemReady(
profile_,
base::BindOnce(
&PreinstalledWebAppManager::Load, weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&PreinstalledWebAppManager::Synchronize,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void PreinstalledWebAppManager::Load(ConsumeInstallOptions callback) {
bool preinstalling_enabled =
base::FeatureList::IsEnabled(features::kPreinstalledWebAppInstallation);
#if BUILDFLAG(IS_CHROMEOS_ASH)
// With Lacros, web apps are not installed using the Ash browser.
if (IsWebAppsCrosapiEnabled())
preinstalling_enabled = false;
#endif
if (!preinstalling_enabled) {
std::move(callback).Run({});
return;
}
LoadConfigs(base::BindOnce(
&PreinstalledWebAppManager::ParseConfigs, weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&PreinstalledWebAppManager::PostProcessConfigs,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void PreinstalledWebAppManager::LoadConfigs(ConsumeLoadedConfigs callback) {
if (g_configs_for_testing) {
LoadedConfigs loaded_configs;
for (const base::Value& config : *g_configs_for_testing) {
auto file = base::FilePath(FILE_PATH_LITERAL("test.json"));
if (g_config_dir_for_testing) {
file = g_config_dir_for_testing->Append(file);
}
loaded_configs.configs.push_back(
{.contents = config.Clone(), .file = file});
}
std::move(callback).Run(std::move(loaded_configs));
return;
}
base::FilePath config_dir = GetConfigDir();
if (config_dir.empty()) {
std::move(callback).Run({});
return;
}
std::vector<base::FilePath> config_dirs = {config_dir};
std::string extra_config_subdir = GetExtraConfigSubdirectory();
if (!extra_config_subdir.empty()) {
config_dirs.push_back(config_dir.AppendASCII(extra_config_subdir));
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&LoadConfigsBlocking, std::move(config_dirs)),
std::move(callback));
}
void PreinstalledWebAppManager::ParseConfigs(ConsumeParsedConfigs callback,
LoadedConfigs loaded_configs) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ParseConfigsBlocking, std::move(loaded_configs)),
std::move(callback));
}
void PreinstalledWebAppManager::PostProcessConfigs(
ConsumeInstallOptions callback,
ParsedConfigs parsed_configs) {
// Add hard coded configs.
for (ExternalInstallOptions& options : GetPreinstalledWebApps())
parsed_configs.options_list.push_back(std::move(options));
// Set common install options.
for (ExternalInstallOptions& options : parsed_configs.options_list) {
DCHECK_EQ(options.install_source, ExternalInstallSource::kExternalDefault);
options.require_manifest = true;
#if defined(OS_CHROMEOS)
// On Chrome OS the "quick launch bar" is the shelf pinned apps.
// This is configured in `GetDefaultPinnedAppsForFormFactor()` instead of
// here to ensure a specific order is deployed.
options.add_to_quick_launch_bar = false;
#else // defined(OS_CHROMEOS)
if (!g_bypass_offline_manifest_requirement_for_testing_) {
// Non-Chrome OS platforms are not permitted to fetch the web app install
// URLs during start up.
DCHECK(options.app_info_factory);
options.only_use_app_info_factory = true;
}
// Preinstalled web apps should not have OS shortcuts of any kind outside of
// Chrome OS.
options.add_to_applications_menu = false;
options.add_to_search = false;
options.add_to_management = false;
options.add_to_desktop = false;
options.add_to_quick_launch_bar = false;
#endif // defined(OS_CHROMEOS)
}
// TODO(crbug.com/1175196): Move this constant into some shared constants.h
// file.
bool preinstalled_apps_enabled_in_prefs =
profile_->GetPrefs()->GetString(prefs::kPreinstalledApps) == "install";
bool is_new_user = IsNewUser();
std::string user_type = apps::DetermineUserType(profile_);
size_t disabled_count = 0;
base::EraseIf(
parsed_configs.options_list, [&](const ExternalInstallOptions& options) {
absl::optional<std::string> disable_reason = GetDisableReason(
options, profile_, registrar_, preinstalled_apps_enabled_in_prefs,
is_new_user, user_type);
if (disable_reason) {
VLOG(1) << *disable_reason;
++disabled_count;
if (debug_info_) {
debug_info_->disabled_configs.emplace_back(
std::move(options), std::move(*disable_reason));
}
return true;
}
return false;
});
if (debug_info_) {
debug_info_->parse_errors = parsed_configs.errors;
debug_info_->enabled_configs = parsed_configs.options_list;
}
// Triggers |force_reinstall| in ExternallyManagedAppManager if milestone
// increments across |force_reinstall_for_milestone|.
for (ExternalInstallOptions& options : parsed_configs.options_list) {
if (options.force_reinstall_for_milestone &&
IsReinstallPastMilestoneNeededSinceLastSync(
options.force_reinstall_for_milestone.value())) {
options.force_reinstall = true;
}
}
UMA_HISTOGRAM_COUNTS_100(kHistogramEnabledCount,
parsed_configs.options_list.size());
UMA_HISTOGRAM_COUNTS_100(kHistogramDisabledCount, disabled_count);
UMA_HISTOGRAM_COUNTS_100(kHistogramConfigErrorCount,
parsed_configs.errors.size());
std::move(callback).Run(parsed_configs.options_list);
}
void PreinstalledWebAppManager::Synchronize(
ExternallyManagedAppManager::SynchronizeCallback callback,
std::vector<ExternalInstallOptions> desired_apps_install_options) {
DCHECK(externally_managed_app_manager_);
std::map<GURL, std::vector<AppId>> desired_uninstalls;
for (const auto& entry : desired_apps_install_options) {
if (!entry.uninstall_and_replace.empty())
desired_uninstalls.emplace(entry.install_url,
entry.uninstall_and_replace);
}
externally_managed_app_manager_->SynchronizeInstalledApps(
std::move(desired_apps_install_options),
ExternalInstallSource::kExternalDefault,
base::BindOnce(&PreinstalledWebAppManager::OnExternalWebAppsSynchronized,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(desired_uninstalls)));
}
void PreinstalledWebAppManager::OnExternalWebAppsSynchronized(
ExternallyManagedAppManager::SynchronizeCallback callback,
std::map<GURL, std::vector<AppId>> desired_uninstalls,
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results,
std::map<GURL, bool> uninstall_results) {
// Note that we are storing the Chrome version (milestone number) instead of a
// "has synchronised" bool in order to do version update specific logic.
profile_->GetPrefs()->SetString(
prefs::kWebAppsLastPreinstallSynchronizeVersion,
version_info::GetMajorVersionNumber());
size_t uninstall_and_replace_count = 0;
for (const auto& url_and_result : install_results) {
UMA_HISTOGRAM_ENUMERATION(kHistogramInstallResult,
url_and_result.second.code);
if (url_and_result.second.did_uninstall_and_replace) {
++uninstall_and_replace_count;
}
// We mark the app as migrated to a web app as long as the installation
// was successful, even if the previous app was not installed. This ensures
// we properly re-install apps if the migration feature is rolled back.
if (IsSuccess(url_and_result.second.code)) {
auto iter = desired_uninstalls.find(url_and_result.first);
if (iter != desired_uninstalls.end()) {
for (const auto& uninstalled_id : iter->second) {
MarkAppAsMigratedToWebApp(profile_, uninstalled_id, true);
}
}
}
}
UMA_HISTOGRAM_COUNTS_100(kHistogramUninstallAndReplaceCount,
uninstall_and_replace_count);
SetMigrationRun(profile_, kMigrateDefaultChromeAppToWebAppsGSuite.name,
IsPreinstalledAppInstallFeatureEnabled(
kMigrateDefaultChromeAppToWebAppsGSuite.name, *profile_));
SetMigrationRun(
profile_, kMigrateDefaultChromeAppToWebAppsNonGSuite.name,
IsPreinstalledAppInstallFeatureEnabled(
kMigrateDefaultChromeAppToWebAppsNonGSuite.name, *profile_));
if (callback) {
std::move(callback).Run(std::move(install_results),
std::move(uninstall_results));
}
}
void PreinstalledWebAppManager::OnStartUpTaskCompleted(
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results,
std::map<GURL, bool> uninstall_results) {
if (debug_info_) {
debug_info_->is_start_up_task_complete = true;
debug_info_->install_results = std::move(install_results);
debug_info_->uninstall_results = std::move(uninstall_results);
}
}
base::FilePath PreinstalledWebAppManager::GetConfigDir() {
std::string command_line_directory = GetConfigDirectoryFromCommandLine();
if (!command_line_directory.empty())
return base::FilePath::FromUTF8Unsafe(command_line_directory);
#if defined(OS_CHROMEOS)
// As of mid 2018, only Chrome OS has default/external web apps, and
// chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS is only defined for OS_LINUX,
// which includes OS_CHROMEOS.
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Exclude sign-in and lock screen profiles.
if (!chromeos::ProfileHelper::IsRegularProfile(profile_)) {
return {};
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
if (g_config_dir_for_testing) {
return *g_config_dir_for_testing;
}
// For manual testing, you can change s/STANDALONE/USER/, as writing to
// "$HOME/.config/chromium/test-user/.config/chromium/External
// Extensions/web_apps" does not require root ACLs, unlike
// "/usr/share/chromium/extensions/web_apps".
base::FilePath dir;
if (base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&dir)) {
return dir.Append(kWebAppsSubDirectory);
}
LOG(ERROR) << "base::PathService::Get failed";
#endif // defined(OS_CHROMEOS)
return {};
}
bool PreinstalledWebAppManager::IsNewUser() {
PrefService* prefs = profile_->GetPrefs();
return prefs->GetString(prefs::kWebAppsLastPreinstallSynchronizeVersion)
.empty();
}
bool PreinstalledWebAppManager::IsReinstallPastMilestoneNeededSinceLastSync(
int force_reinstall_for_milestone) {
PrefService* prefs = profile_->GetPrefs();
std::string last_preinstall_synchronize_milestone =
prefs->GetString(prefs::kWebAppsLastPreinstallSynchronizeVersion);
return IsReinstallPastMilestoneNeeded(last_preinstall_synchronize_milestone,
version_info::GetMajorVersionNumber(),
force_reinstall_for_milestone);
}
PreinstalledWebAppManager::DebugInfo::DebugInfo() = default;
PreinstalledWebAppManager::DebugInfo::~DebugInfo() = default;
} // namespace web_app