Commit 440d8bdc authored by Alan Cutter's avatar Alan Cutter Committed by Commit Bot

Fix and test default web app uninstall_and_replace config parameter

This CL adds a basic test for the uninstall_and_replace feature of
ExternalWebAppManager and fixes two issues:
 - On Chrome OS the uninstall was getting blocked on a user prompt.
 - On non-Chrome OS the uninstall never happened.

The changes in this CL are:
 - Use AppServiceProxy::UninstallSilently() to avoid user prompt.
 - Move ExtensionAppsChromeOs::Uninstall() to ExtensionAppsBase.
 - Instantiate ExtensionApps publisher for non-Chrome OS app service.
 - Refactor ExternalWebAppManager's ParseConfig() out of ScanDir()
   and expose it for testing.

Bug: 809304, 1058265
Change-Id: I86caeeb072ec18a4c16a181a01f3c795d2735648
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2306139
Commit-Queue: Alan Cutter <alancutter@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Reviewed-by: default avatarNancy Wang <nancylingwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790233}
parent aff539f1
...@@ -201,6 +201,8 @@ void AppServiceProxy::Initialize() { ...@@ -201,6 +201,8 @@ void AppServiceProxy::Initialize() {
extension_web_apps_ = std::make_unique<ExtensionApps>( extension_web_apps_ = std::make_unique<ExtensionApps>(
app_service_, profile_, apps::mojom::AppType::kWeb); app_service_, profile_, apps::mojom::AppType::kWeb);
} }
extension_apps_ = std::make_unique<ExtensionApps>(
app_service_, profile_, apps::mojom::AppType::kExtension);
#endif #endif
// Asynchronously add app icon source, so we don't do too much work in the // Asynchronously add app icon source, so we don't do too much work in the
......
...@@ -435,6 +435,7 @@ class AppServiceProxy : public KeyedService, ...@@ -435,6 +435,7 @@ class AppServiceProxy : public KeyedService,
// TODO(crbug.com/877898): Erase extension_web_apps_ when BMO is on. // TODO(crbug.com/877898): Erase extension_web_apps_ when BMO is on.
std::unique_ptr<ExtensionApps> extension_web_apps_; std::unique_ptr<ExtensionApps> extension_web_apps_;
std::unique_ptr<WebApps> web_apps_; std::unique_ptr<WebApps> web_apps_;
std::unique_ptr<ExtensionApps> extension_apps_;
#endif #endif
Profile* profile_; Profile* profile_;
......
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#include "components/content_settings/core/common/content_settings_pattern.h" #include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h" #include "components/content_settings/core/common/content_settings_types.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h" #include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "content/public/browser/clear_site_data_utils.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_system.h" #include "extensions/browser/extension_system.h"
#include "extensions/browser/ui_util.h" #include "extensions/browser/ui_util.h"
...@@ -602,6 +603,82 @@ void ExtensionAppsBase::SetPermission(const std::string& app_id, ...@@ -602,6 +603,82 @@ void ExtensionAppsBase::SetPermission(const std::string& app_id,
url, url, permission_type, std::string() /* resource identifier */, url, url, permission_type, std::string() /* resource identifier */,
permission_value); permission_value);
} }
void ExtensionAppsBase::Uninstall(const std::string& app_id,
bool clear_site_data,
bool report_abuse) {
// TODO(crbug.com/1009248): We need to add the error code, which could be used
// by ExtensionFunction, ManagementUninstallFunctionBase on the callback
// OnExtensionUninstallDialogClosed
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionRegistry::Get(profile())->GetInstalledExtension(
app_id);
if (!extension.get()) {
return;
}
base::string16 error;
extensions::ExtensionSystem::Get(profile())
->extension_service()
->UninstallExtension(app_id, extensions::UNINSTALL_REASON_USER_INITIATED,
&error);
if (extension->from_bookmark()) {
if (!clear_site_data) {
UMA_HISTOGRAM_ENUMERATION(
"Webapp.UninstallDialogAction",
extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
return;
}
UMA_HISTOGRAM_ENUMERATION(
"Webapp.UninstallDialogAction",
extensions::ExtensionUninstallDialog::
CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
constexpr bool kClearCookies = true;
constexpr bool kClearStorage = true;
constexpr bool kClearCache = true;
constexpr bool kAvoidClosingConnections = false;
content::ClearSiteData(
base::BindRepeating(
[](content::BrowserContext* browser_context) {
return browser_context;
},
base::Unretained(profile())),
url::Origin::Create(
extensions::AppLaunchInfo::GetFullLaunchURL(extension.get())),
kClearCookies, kClearStorage, kClearCache, kAvoidClosingConnections,
base::DoNothing());
} else {
if (!report_abuse) {
UMA_HISTOGRAM_ENUMERATION(
"Extensions.UninstallDialogAction",
extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
return;
}
UMA_HISTOGRAM_ENUMERATION(
"Extensions.UninstallDialogAction",
extensions::ExtensionUninstallDialog::
CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
// If the extension specifies a custom uninstall page via
// chrome.runtime.setUninstallURL, then at uninstallation its uninstall
// page opens. To ensure that the CWS Report Abuse page is the active
// tab at uninstallation, navigates to the url to report abuse.
constexpr char kReferrerId[] = "chrome-remove-extension-dialog";
NavigateParams params(
profile(),
extension_urls::GetWebstoreReportAbuseUrl(app_id, kReferrerId),
ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
Navigate(&params);
}
}
void ExtensionAppsBase::OpenNativeSettings(const std::string& app_id) { void ExtensionAppsBase::OpenNativeSettings(const std::string& app_id) {
const auto* extension = MaybeGetExtension(app_id); const auto* extension = MaybeGetExtension(app_id);
......
...@@ -145,6 +145,9 @@ class ExtensionAppsBase : public apps::PublisherBase, ...@@ -145,6 +145,9 @@ class ExtensionAppsBase : public apps::PublisherBase,
int64_t display_id) override; int64_t display_id) override;
void SetPermission(const std::string& app_id, void SetPermission(const std::string& app_id,
apps::mojom::PermissionPtr permission) override; apps::mojom::PermissionPtr permission) override;
void Uninstall(const std::string& app_id,
bool clear_site_data,
bool report_abuse) override;
void OpenNativeSettings(const std::string& app_id) override; void OpenNativeSettings(const std::string& app_id) override;
// content_settings::Observer overrides. // content_settings::Observer overrides.
......
...@@ -201,83 +201,6 @@ void ExtensionAppsChromeOs::LaunchAppWithIntent( ...@@ -201,83 +201,6 @@ void ExtensionAppsChromeOs::LaunchAppWithIntent(
} }
} }
void ExtensionAppsChromeOs::Uninstall(const std::string& app_id,
bool clear_site_data,
bool report_abuse) {
// TODO(crbug.com/1009248): We need to add the error code, which could be used
// by ExtensionFunction, ManagementUninstallFunctionBase on the callback
// OnExtensionUninstallDialogClosed
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionRegistry::Get(profile())->GetInstalledExtension(
app_id);
if (!extension.get()) {
return;
}
base::string16 error;
extensions::ExtensionSystem::Get(profile())
->extension_service()
->UninstallExtension(app_id, extensions::UNINSTALL_REASON_USER_INITIATED,
&error);
if (extension->from_bookmark()) {
if (!clear_site_data) {
UMA_HISTOGRAM_ENUMERATION(
"Webapp.UninstallDialogAction",
extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
return;
}
UMA_HISTOGRAM_ENUMERATION(
"Webapp.UninstallDialogAction",
extensions::ExtensionUninstallDialog::
CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
constexpr bool kClearCookies = true;
constexpr bool kClearStorage = true;
constexpr bool kClearCache = true;
constexpr bool kAvoidClosingConnections = false;
content::ClearSiteData(
base::BindRepeating(
[](content::BrowserContext* browser_context) {
return browser_context;
},
base::Unretained(profile())),
url::Origin::Create(
extensions::AppLaunchInfo::GetFullLaunchURL(extension.get())),
kClearCookies, kClearStorage, kClearCache, kAvoidClosingConnections,
base::DoNothing());
} else {
if (!report_abuse) {
UMA_HISTOGRAM_ENUMERATION(
"Extensions.UninstallDialogAction",
extensions::ExtensionUninstallDialog::CLOSE_ACTION_UNINSTALL,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
return;
}
UMA_HISTOGRAM_ENUMERATION(
"Extensions.UninstallDialogAction",
extensions::ExtensionUninstallDialog::
CLOSE_ACTION_UNINSTALL_AND_CHECKBOX_CHECKED,
extensions::ExtensionUninstallDialog::CLOSE_ACTION_LAST);
// If the extension specifies a custom uninstall page via
// chrome.runtime.setUninstallURL, then at uninstallation its uninstall
// page opens. To ensure that the CWS Report Abuse page is the active
// tab at uninstallation, navigates to the url to report abuse.
constexpr char kReferrerId[] = "chrome-remove-extension-dialog";
NavigateParams params(
profile(),
extension_urls::GetWebstoreReportAbuseUrl(app_id, kReferrerId),
ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
Navigate(&params);
}
}
void ExtensionAppsChromeOs::PauseApp(const std::string& app_id) { void ExtensionAppsChromeOs::PauseApp(const std::string& app_id) {
if (paused_apps_.MaybeAddApp(app_id)) { if (paused_apps_.MaybeAddApp(app_id)) {
SetIconEffect(app_id); SetIconEffect(app_id);
......
...@@ -78,9 +78,6 @@ class ExtensionAppsChromeOs : public ExtensionAppsBase, ...@@ -78,9 +78,6 @@ class ExtensionAppsChromeOs : public ExtensionAppsBase,
apps::mojom::IntentPtr intent, apps::mojom::IntentPtr intent,
apps::mojom::LaunchSource launch_source, apps::mojom::LaunchSource launch_source,
int64_t display_id) override; int64_t display_id) override;
void Uninstall(const std::string& app_id,
bool clear_site_data,
bool report_abuse) override;
void PauseApp(const std::string& app_id) override; void PauseApp(const std::string& app_id) override;
void UnpauseApps(const std::string& app_id) override; void UnpauseApps(const std::string& app_id) override;
void GetMenuModel(const std::string& app_id, void GetMenuModel(const std::string& app_id,
......
...@@ -134,7 +134,7 @@ void WebAppUiManagerImpl::UninstallAndReplace( ...@@ -134,7 +134,7 @@ void WebAppUiManagerImpl::UninstallAndReplace(
apps::AppServiceProxy* proxy = apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile_); apps::AppServiceProxyFactory::GetForProfile(profile_);
DCHECK(proxy); DCHECK(proxy);
proxy->Uninstall(from_app, nullptr /* parent_window */); proxy->UninstallSilently(from_app);
} }
} }
......
...@@ -245,6 +245,7 @@ source_set("web_applications_browser_tests") { ...@@ -245,6 +245,7 @@ source_set("web_applications_browser_tests") {
testonly = true testonly = true
sources = [ sources = [
"external_web_app_manager_browsertest.cc",
"manifest_update_manager_browsertest.cc", "manifest_update_manager_browsertest.cc",
"pending_app_manager_impl_browsertest.cc", "pending_app_manager_impl_browsertest.cc",
"system_web_app_manager_browsertest.cc", "system_web_app_manager_browsertest.cc",
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/files/file_enumerator.h" #include "base/files/file_enumerator.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h" #include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
...@@ -81,6 +82,8 @@ const base::FilePath::CharType kWebAppsSubDirectory[] = ...@@ -81,6 +82,8 @@ const base::FilePath::CharType kWebAppsSubDirectory[] =
FILE_PATH_LITERAL("web_apps"); FILE_PATH_LITERAL("web_apps");
#endif #endif
bool g_skip_startup_scan_for_testing_ = false;
bool IsFeatureEnabled(const std::string& feature_name) { bool IsFeatureEnabled(const std::string& feature_name) {
// The feature system ensures there is only ever one Feature instance for each // The feature system ensures there is only ever one Feature instance for each
// given feature name. To enable multiple apps to be gated by the same field // given feature name. To enable multiple apps to be gated by the same field
...@@ -105,6 +108,120 @@ bool IsFeatureEnabled(const std::string& feature_name) { ...@@ -105,6 +108,120 @@ bool IsFeatureEnabled(const std::string& feature_name) {
return base::FeatureList::IsEnabled(*it->second); return base::FeatureList::IsEnabled(*it->second);
} }
base::Optional<ExternalInstallOptions> ParseConfig(
base::FilePath file,
const std::string& user_type,
const base::Value& app_config) {
if (app_config.type() != base::Value::Type::DICTIONARY) {
LOG(ERROR) << file << " was not a dictionary as the top level";
return base::nullopt;
}
if (!apps::UserTypeMatchesJsonUserType(
user_type, /*app_id=*/file.MaybeAsASCII(), &app_config,
/*default_user_types=*/nullptr)) {
// Already logged.
return base::nullopt;
}
const base::Value* value =
app_config.FindKeyOfType(kFeatureName, base::Value::Type::STRING);
if (value) {
std::string feature_name = value->GetString();
VLOG(1) << file << " checking feature " << feature_name;
if (!IsFeatureEnabled(feature_name)) {
VLOG(1) << file << " feature not enabled";
return base::nullopt;
}
}
value = app_config.FindKeyOfType(kAppUrl, base::Value::Type::STRING);
if (!value) {
LOG(ERROR) << file << " had a missing " << kAppUrl;
return base::nullopt;
}
GURL app_url(value->GetString());
if (!app_url.is_valid()) {
LOG(ERROR) << file << " had an invalid " << kAppUrl;
return base::nullopt;
}
bool hide_from_user = false;
value = app_config.FindKey(kHideFromUser);
if (value) {
if (!value->is_bool()) {
LOG(ERROR) << file << " had an invalid " << kHideFromUser;
return base::nullopt;
}
hide_from_user = value->GetBool();
}
bool create_shortcuts = false;
value = app_config.FindKey(kCreateShortcuts);
if (value) {
if (!value->is_bool()) {
LOG(ERROR) << file << " had an invalid " << kCreateShortcuts;
return base::nullopt;
}
create_shortcuts = value->GetBool();
}
// It doesn't make sense to hide the app and also create shortcuts for it.
DCHECK(!(hide_from_user && create_shortcuts));
value = app_config.FindKeyOfType(kLaunchContainer, base::Value::Type::STRING);
if (!value) {
LOG(ERROR) << file << " had an invalid " << kLaunchContainer;
return base::nullopt;
}
std::string launch_container_str = value->GetString();
auto user_display_mode = DisplayMode::kBrowser;
if (launch_container_str == kLaunchContainerTab) {
user_display_mode = DisplayMode::kBrowser;
} else if (launch_container_str == kLaunchContainerWindow) {
user_display_mode = DisplayMode::kStandalone;
} else {
LOG(ERROR) << file << " had an invalid " << kLaunchContainer;
return base::nullopt;
}
value = app_config.FindKey(kUninstallAndReplace);
std::vector<AppId> uninstall_and_replace_ids;
if (value) {
if (!value->is_list()) {
LOG(ERROR) << file << " had an invalid " << kUninstallAndReplace;
return base::nullopt;
}
base::Value::ConstListView uninstall_and_replace_values = value->GetList();
bool had_error = false;
for (const auto& app_id_value : uninstall_and_replace_values) {
if (!app_id_value.is_string()) {
had_error = true;
LOG(ERROR) << file << " had an invalid " << kUninstallAndReplace
<< " entry";
break;
}
uninstall_and_replace_ids.push_back(app_id_value.GetString());
}
if (had_error)
return base::nullopt;
}
ExternalInstallOptions install_options(
std::move(app_url), user_display_mode,
ExternalInstallSource::kExternalDefault);
install_options.add_to_applications_menu = !hide_from_user;
install_options.add_to_search = !hide_from_user;
install_options.add_to_management = !hide_from_user;
install_options.add_to_desktop = create_shortcuts;
install_options.add_to_quick_launch_bar = create_shortcuts;
install_options.require_manifest = true;
install_options.uninstall_and_replace = std::move(uninstall_and_replace_ids);
return install_options;
}
std::vector<ExternalInstallOptions> ScanDir(const base::FilePath& dir, std::vector<ExternalInstallOptions> ScanDir(const base::FilePath& dir,
const std::string& user_type) { const std::string& user_type) {
std::vector<ExternalInstallOptions> install_options_list; std::vector<ExternalInstallOptions> install_options_list;
...@@ -126,123 +243,16 @@ std::vector<ExternalInstallOptions> ScanDir(const base::FilePath& dir, ...@@ -126,123 +243,16 @@ std::vector<ExternalInstallOptions> ScanDir(const base::FilePath& dir,
JSONFileValueDeserializer deserializer(file); JSONFileValueDeserializer deserializer(file);
std::string error_msg; std::string error_msg;
std::unique_ptr<base::Value> dict = std::unique_ptr<base::Value> app_config =
deserializer.Deserialize(nullptr, &error_msg); deserializer.Deserialize(nullptr, &error_msg);
if (!dict) { if (!app_config) {
LOG(ERROR) << file.value() << " was not valid JSON: " << error_msg; LOG(ERROR) << file.value() << " was not valid JSON: " << error_msg;
continue; continue;
} }
if (dict->type() != base::Value::Type::DICTIONARY) { base::Optional<ExternalInstallOptions> install_options =
LOG(ERROR) << file.value() << " was not a dictionary as the top level"; ParseConfig(file, user_type, *app_config);
continue; if (install_options.has_value())
} install_options_list.push_back(std::move(*install_options));
if (!apps::UserTypeMatchesJsonUserType(
user_type, file.MaybeAsASCII() /* app_id */, dict.get(),
nullptr /* default_user_types */)) {
// Already logged.
continue;
}
const base::Value* value =
dict->FindKeyOfType(kFeatureName, base::Value::Type::STRING);
if (value) {
std::string feature_name = value->GetString();
VLOG(1) << file.value() << " checking feature " << feature_name;
if (!IsFeatureEnabled(feature_name)) {
VLOG(1) << file.value() << " feature not enabled";
continue;
}
}
value = dict->FindKeyOfType(kAppUrl, base::Value::Type::STRING);
if (!value) {
LOG(ERROR) << file.value() << " had a missing " << kAppUrl;
continue;
}
GURL app_url(value->GetString());
if (!app_url.is_valid()) {
LOG(ERROR) << file.value() << " had an invalid " << kAppUrl;
continue;
}
bool hide_from_user = false;
value = dict->FindKey(kHideFromUser);
if (value) {
if (!value->is_bool()) {
LOG(ERROR) << file.value() << " had an invalid " << kHideFromUser;
continue;
}
hide_from_user = value->GetBool();
}
bool create_shortcuts = false;
value = dict->FindKey(kCreateShortcuts);
if (value) {
if (!value->is_bool()) {
LOG(ERROR) << file.value() << " had an invalid " << kCreateShortcuts;
continue;
}
create_shortcuts = value->GetBool();
}
// It doesn't make sense to hide the app and also create shortcuts for it.
DCHECK(!(hide_from_user && create_shortcuts));
value = dict->FindKeyOfType(kLaunchContainer, base::Value::Type::STRING);
if (!value) {
LOG(ERROR) << file.value() << " had an invalid " << kLaunchContainer;
continue;
}
std::string launch_container_str = value->GetString();
auto user_display_mode = DisplayMode::kBrowser;
if (launch_container_str == kLaunchContainerTab) {
user_display_mode = DisplayMode::kBrowser;
} else if (launch_container_str == kLaunchContainerWindow) {
user_display_mode = DisplayMode::kStandalone;
} else {
LOG(ERROR) << file.value() << " had an invalid " << kLaunchContainer;
continue;
}
value = dict->FindKey(kUninstallAndReplace);
std::vector<AppId> uninstall_and_replace_ids;
if (value) {
if (!value->is_list()) {
LOG(ERROR) << file.value() << " had an invalid "
<< kUninstallAndReplace;
continue;
}
base::Value::ConstListView uninstall_and_replace_values =
value->GetList();
bool had_error = false;
for (const auto& app_id_value : uninstall_and_replace_values) {
if (!app_id_value.is_string()) {
had_error = true;
LOG(ERROR) << file.value() << " had an invalid "
<< kUninstallAndReplace << " entry";
break;
}
uninstall_and_replace_ids.push_back(app_id_value.GetString());
}
if (had_error)
continue;
}
ExternalInstallOptions install_options(
std::move(app_url), user_display_mode,
ExternalInstallSource::kExternalDefault);
install_options.add_to_applications_menu = !hide_from_user;
install_options.add_to_search = !hide_from_user;
install_options.add_to_management = !hide_from_user;
install_options.add_to_desktop = create_shortcuts;
install_options.add_to_quick_launch_bar = create_shortcuts;
install_options.require_manifest = true;
install_options.uninstall_and_replace =
std::move(uninstall_and_replace_ids);
install_options_list.push_back(std::move(install_options));
} }
return install_options_list; return install_options_list;
...@@ -292,9 +302,11 @@ void ExternalWebAppManager::SetSubsystems( ...@@ -292,9 +302,11 @@ void ExternalWebAppManager::SetSubsystems(
} }
void ExternalWebAppManager::Start() { void ExternalWebAppManager::Start() {
ScanForExternalWebApps( if (!g_skip_startup_scan_for_testing_) {
base::BindOnce(&ExternalWebAppManager::OnScanForExternalWebApps, ScanForExternalWebApps(
weak_ptr_factory_.GetWeakPtr())); base::BindOnce(&ExternalWebAppManager::OnScanForExternalWebApps,
weak_ptr_factory_.GetWeakPtr()));
}
} }
// static // static
...@@ -329,6 +341,32 @@ void ExternalWebAppManager::ScanForExternalWebApps(ScanCallback callback) { ...@@ -329,6 +341,32 @@ void ExternalWebAppManager::ScanForExternalWebApps(ScanCallback callback) {
std::move(callback)); std::move(callback));
} }
void ExternalWebAppManager::SkipStartupScanForTesting() {
g_skip_startup_scan_for_testing_ = true;
}
void ExternalWebAppManager::SynchronizeAppsForTesting(
std::vector<std::string> app_configs,
PendingAppManager::SynchronizeCallback callback) {
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(base::FilePath().AppendASCII("test"),
apps::DetermineUserType(profile_), *app_config);
DCHECK(install_options);
install_options_list.push_back(std::move(*install_options));
}
pending_app_manager_->SynchronizeInstalledApps(
std::move(install_options_list), ExternalInstallSource::kExternalDefault,
std::move(callback));
}
void ExternalWebAppManager::OnScanForExternalWebApps( void ExternalWebAppManager::OnScanForExternalWebApps(
std::vector<ExternalInstallOptions> desired_apps_install_options) { std::vector<ExternalInstallOptions> desired_apps_install_options) {
DCHECK(pending_app_manager_); DCHECK(pending_app_manager_);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "chrome/browser/web_applications/components/external_install_options.h" #include "chrome/browser/web_applications/components/external_install_options.h"
#include "chrome/browser/web_applications/components/pending_app_manager.h"
namespace base { namespace base {
class FilePath; class FilePath;
...@@ -45,6 +46,12 @@ class ExternalWebAppManager { ...@@ -45,6 +46,12 @@ class ExternalWebAppManager {
void ScanForExternalWebApps(ScanCallback callback); void ScanForExternalWebApps(ScanCallback callback);
static void SkipStartupScanForTesting();
void SynchronizeAppsForTesting(
std::vector<std::string> app_configs,
PendingAppManager::SynchronizeCallback callback);
private: private:
void OnScanForExternalWebApps(std::vector<ExternalInstallOptions>); void OnScanForExternalWebApps(std::vector<ExternalInstallOptions>);
......
// Copyright 2020 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 "base/strings/string_util.h"
#include "base/test/bind_test_util.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kChromeAppDirectory[] = "app";
const char kChromeAppName[] = "App Test";
} // namespace
namespace web_app {
class ExternalWebAppManagerBrowserTest
: public extensions::ExtensionBrowserTest {
public:
ExternalWebAppManagerBrowserTest() {
ExternalWebAppManager::SkipStartupScanForTesting();
}
GURL GetAppUrl() const {
return embedded_test_server()->GetURL("/web_apps/basic.html");
}
~ExternalWebAppManagerBrowserTest() override = default;
};
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest, UninstallAndReplace) {
ASSERT_TRUE(embedded_test_server()->Start());
Profile* profile = browser()->profile();
// Install Chrome app to be replaced.
const extensions::Extension* app = InstallExtensionWithSourceAndFlags(
test_data_dir_.AppendASCII(kChromeAppDirectory), 1,
extensions::Manifest::INTERNAL, extensions::Extension::NO_FLAGS);
EXPECT_EQ(app->name(), kChromeAppName);
// Start listening for Chrome app uninstall.
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile));
// Trigger default web app install.
base::RunLoop sync_run_loop;
WebAppProvider::Get(profile)
->external_web_app_manager_for_testing()
.SynchronizeAppsForTesting(
{base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"uninstall_and_replace": ["$2"]
})",
{GetAppUrl().spec(), app->id()}, nullptr)},
base::BindLambdaForTesting(
[&](std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
EXPECT_EQ(install_results.at(GetAppUrl()),
InstallResultCode::kSuccessNewInstall);
sync_run_loop.Quit();
}));
sync_run_loop.Run();
// Chrome app should get uninstalled.
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(app, uninstalled_app.get());
}
} // namespace web_app
...@@ -94,6 +94,10 @@ class WebAppProvider : public WebAppProviderBase { ...@@ -94,6 +94,10 @@ class WebAppProvider : public WebAppProviderBase {
return on_registry_ready_; return on_registry_ready_;
} }
ExternalWebAppManager& external_web_app_manager_for_testing() {
return *external_web_app_manager_;
}
protected: protected:
virtual void StartImpl(); virtual void StartImpl();
void OnDatabaseMigrationCompleted(bool success); void OnDatabaseMigrationCompleted(bool success);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment