blob: 36884437c70e04afce19ed1bb97754dccf308624 [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/external_web_app_manager.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.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/no_destructor.h"
#include "base/path_service.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 "chrome/browser/apps/user_type_filter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/pending_app_manager.h"
#include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/browser/web_applications/components/web_app_install_utils.h"
#include "chrome/browser/web_applications/external_web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#endif
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
bool g_skip_startup_scan_for_testing_ = false;
std::vector<ExternalInstallOptions> ScanDir(
std::unique_ptr<FileUtilsWrapper> file_utils,
const base::FilePath& dir,
const std::string& user_type) {
std::vector<ExternalInstallOptions> install_options_list;
if (!base::FeatureList::IsEnabled(features::kDefaultWebAppInstallation))
return install_options_list;
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::FilePath::StringType extension(FILE_PATH_LITERAL(".json"));
base::FileEnumerator json_files(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) {
LOG(ERROR) << file.value() << " was not valid JSON: " << error_msg;
continue;
}
base::Optional<ExternalInstallOptions> install_options =
ParseConfig(*file_utils, dir, file, user_type, *app_config);
if (install_options.has_value())
install_options_list.push_back(std::move(*install_options));
}
return install_options_list;
}
base::FilePath DetermineScanDir(const Profile* profile) {
base::FilePath dir;
#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 (chromeos::ProfileHelper::IsPrimaryProfile(profile)) {
// 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".
if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&dir)) {
LOG(ERROR) << "ScanForExternalWebApps: base::PathService::Get failed";
} else {
dir = dir.Append(kWebAppsSubDirectory);
}
}
#endif
return dir;
}
void OnExternalWebAppsSynchronized(
std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
RecordExternalAppInstallResultCode("Webapp.InstallResult.Default",
install_results);
}
std::vector<ExternalInstallOptions> SynchronizeAppsBlockingForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
std::vector<std::string> app_configs,
const std::string& user_type) {
std::vector<ExternalInstallOptions> install_options_list;
for (const std::string& app_config_string : app_configs) {
base::Optional<base::Value> app_config =
base::JSONReader::Read(app_config_string);
DCHECK(app_config);
base::Optional<ExternalInstallOptions> install_options =
ParseConfig(*file_utils, base::FilePath(FILE_PATH_LITERAL("test_dir")),
base::FilePath(FILE_PATH_LITERAL("test_dir/test.json")),
user_type, *app_config);
if (install_options)
install_options_list.push_back(std::move(*install_options));
}
return install_options_list;
}
} // namespace
ExternalWebAppManager::ExternalWebAppManager(Profile* profile)
: profile_(profile) {}
ExternalWebAppManager::~ExternalWebAppManager() = default;
void ExternalWebAppManager::SetSubsystems(
PendingAppManager* pending_app_manager) {
pending_app_manager_ = pending_app_manager;
}
void ExternalWebAppManager::Start() {
if (!g_skip_startup_scan_for_testing_) {
ScanForExternalWebApps(base::BindOnce(
&ExternalWebAppManager::SynchronizeExternalInstallOptions,
weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&OnExternalWebAppsSynchronized)));
}
}
// static
std::vector<ExternalInstallOptions>
ExternalWebAppManager::ScanDirForExternalWebAppsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
const base::FilePath& dir,
Profile* profile) {
return ScanDir(std::move(file_utils), dir, apps::DetermineUserType(profile));
}
void ExternalWebAppManager::ScanForExternalWebApps(ScanCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::FilePath dir = DetermineScanDir(profile_);
if (dir.empty()) {
std::move(callback).Run(std::vector<ExternalInstallOptions>());
return;
}
// Do a two-part callback dance, across different TaskRunners.
//
// 1. Schedule ScanDir to happen on a background thread, so that we don't
// block the UI thread. When that's done,
// base::PostTaskAndReplyWithResult will bounce us back to the originating
// thread (the UI thread).
//
// 2. In |callback|, forward the vector of ExternalInstallOptions on to the
// pending_app_manager_, which can only be called on the UI thread.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ScanDir, std::make_unique<FileUtilsWrapper>(),
std::move(dir), apps::DetermineUserType(profile_)),
std::move(callback));
}
void ExternalWebAppManager::SkipStartupScanForTesting() {
g_skip_startup_scan_for_testing_ = true;
}
void ExternalWebAppManager::SynchronizeAppsForTesting(
std::unique_ptr<FileUtilsWrapper> file_utils,
std::vector<std::string> app_configs,
PendingAppManager::SynchronizeCallback callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&SynchronizeAppsBlockingForTesting, std::move(file_utils),
std::move(app_configs), apps::DetermineUserType(profile_)),
base::BindOnce(&ExternalWebAppManager::SynchronizeExternalInstallOptions,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ExternalWebAppManager::SynchronizeExternalInstallOptions(
PendingAppManager::SynchronizeCallback callback,
std::vector<ExternalInstallOptions> desired_apps_install_options) {
DCHECK(pending_app_manager_);
pending_app_manager_->SynchronizeInstalledApps(
std::move(desired_apps_install_options),
ExternalInstallSource::kExternalDefault, std::move(callback));
}
} // namespace web_app