| // Copyright 2019 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/web_app_shortcut_manager.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_path.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/components/protocol_handler_manager.h" |
| #include "chrome/browser/web_applications/components/web_app_shortcut.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_file_handler_manager.h" |
| #include "chrome/browser/web_applications/web_app_icon_manager.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/services/app_service/public/cpp/file_handler.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/re2/src/re2/re2.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_rep_default.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| #if defined(OS_LINUX) |
| // Aligns with other platform implementations that only support 10 items. |
| constexpr int kMaxApplicationDockMenuItems = 10; |
| #endif // defined(OS_LINUX) |
| |
| // UMA metric name for shortcuts creation result. |
| constexpr const char* kCreationResultMetric = |
| "WebApp.Shortcuts.Creation.Result"; |
| |
| // Result of shortcuts creation process. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class CreationResult { |
| kSuccess = 0, |
| kFailToCreateShortcut = 1, |
| kMaxValue = kFailToCreateShortcut |
| }; |
| |
| WebAppShortcutManager::ShortcutCallback& GetShortcutUpdateCallbackForTesting() { |
| static base::NoDestructor<WebAppShortcutManager::ShortcutCallback> callback; |
| return *callback; |
| } |
| |
| } // namespace |
| |
| WebAppShortcutManager::WebAppShortcutManager( |
| Profile* profile, |
| WebAppIconManager* icon_manager, |
| WebAppFileHandlerManager* file_handler_manager, |
| ProtocolHandlerManager* protocol_handler_manager) |
| : profile_(profile), |
| icon_manager_(icon_manager), |
| file_handler_manager_(file_handler_manager), |
| protocol_handler_manager_(protocol_handler_manager) {} |
| |
| WebAppShortcutManager::~WebAppShortcutManager() = default; |
| |
| void WebAppShortcutManager::SetSubsystems(WebAppIconManager* icon_manager, |
| WebAppRegistrar* registrar) { |
| icon_manager_ = icon_manager; |
| registrar_ = registrar; |
| } |
| |
| void WebAppShortcutManager::UpdateShortcuts(const AppId& app_id, |
| base::StringPiece old_name) { |
| if (!CanCreateShortcuts()) |
| return; |
| |
| GetShortcutInfoForApp( |
| app_id, |
| base::BindOnce( |
| &WebAppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts, |
| weak_ptr_factory_.GetWeakPtr(), base::UTF8ToUTF16(old_name))); |
| } |
| |
| void WebAppShortcutManager::GetAppExistingShortCutLocation( |
| ShortcutLocationCallback callback, |
| std::unique_ptr<ShortcutInfo> shortcut_info) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // Ownership of |shortcut_info| moves to the Reply, which is guaranteed to |
| // outlive the const reference. |
| const ShortcutInfo& shortcut_info_ref = *shortcut_info; |
| internals::GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&internals::GetAppExistingShortCutLocationImpl, |
| std::cref(shortcut_info_ref)), |
| base::BindOnce( |
| [](std::unique_ptr<ShortcutInfo> shortcut_info, |
| ShortcutLocationCallback callback, ShortcutLocations locations) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| shortcut_info.reset(); |
| std::move(callback).Run(locations); |
| }, |
| std::move(shortcut_info), std::move(callback))); |
| } |
| |
| void WebAppShortcutManager::SetShortcutUpdateCallbackForTesting( |
| base::OnceCallback<void(const ShortcutInfo*)> callback) { |
| GetShortcutUpdateCallbackForTesting() = std::move(callback); // IN-TEST |
| } |
| |
| bool WebAppShortcutManager::CanCreateShortcuts() const { |
| #if defined(OS_CHROMEOS) |
| return false; |
| #else |
| return true; |
| #endif |
| } |
| |
| void WebAppShortcutManager::SuppressShortcutsForTesting() { |
| suppress_shortcuts_for_testing_ = true; |
| } |
| |
| void WebAppShortcutManager::CreateShortcuts(const AppId& app_id, |
| bool add_to_desktop, |
| CreateShortcutsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(CanCreateShortcuts()); |
| |
| GetShortcutInfoForApp( |
| app_id, |
| base::BindOnce( |
| &WebAppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts, |
| weak_ptr_factory_.GetWeakPtr(), add_to_desktop, |
| base::BindOnce(&WebAppShortcutManager::OnShortcutsCreated, |
| weak_ptr_factory_.GetWeakPtr(), app_id, |
| std::move(callback)))); |
| } |
| |
| void WebAppShortcutManager::DeleteShortcuts( |
| const AppId& app_id, |
| const base::FilePath& shortcuts_data_dir, |
| std::unique_ptr<ShortcutInfo> shortcut_info, |
| DeleteShortcutsCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| DCHECK(CanCreateShortcuts()); |
| |
| internals::ScheduleDeletePlatformShortcuts( |
| shortcuts_data_dir, std::move(shortcut_info), |
| base::BindOnce(&WebAppShortcutManager::OnShortcutsDeleted, |
| weak_ptr_factory_.GetWeakPtr(), app_id, |
| std::move(callback))); |
| } |
| |
| void WebAppShortcutManager::ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu( |
| const AppId& app_id, |
| RegisterShortcutsMenuCallback callback) { |
| icon_manager_->ReadAllShortcutsMenuIcons( |
| app_id, |
| base::BindOnce( |
| &WebAppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu, |
| weak_ptr_factory_.GetWeakPtr(), app_id, std::move(callback))); |
| } |
| |
| void WebAppShortcutManager::RegisterShortcutsMenuWithOs( |
| const AppId& app_id, |
| const std::vector<WebApplicationShortcutsMenuItemInfo>& |
| shortcuts_menu_item_infos, |
| const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!web_app::ShouldRegisterShortcutsMenuWithOs() || |
| suppress_shortcuts_for_testing_) { |
| return; |
| } |
| |
| std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id); |
| if (!shortcut_info) |
| return; |
| |
| // |shortcut_data_dir| is located in per-app OS integration resources |
| // directory. See GetOsIntegrationResourcesDirectoryForApp function for more |
| // info. |
| base::FilePath shortcut_data_dir = |
| internals::GetShortcutDataDir(*shortcut_info); |
| web_app::RegisterShortcutsMenuWithOs( |
| shortcut_info->extension_id, shortcut_info->profile_path, |
| shortcut_data_dir, shortcuts_menu_item_infos, |
| shortcuts_menu_icon_bitmaps); |
| } |
| |
| void WebAppShortcutManager::UnregisterShortcutsMenuWithOs(const AppId& app_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!web_app::ShouldRegisterShortcutsMenuWithOs()) |
| return; |
| |
| web_app::UnregisterShortcutsMenuWithOs(app_id, profile_->GetPath()); |
| } |
| |
| void WebAppShortcutManager::OnShortcutsCreated(const AppId& app_id, |
| CreateShortcutsCallback callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| UMA_HISTOGRAM_ENUMERATION(kCreationResultMetric, |
| success ? CreationResult::kSuccess |
| : CreationResult::kFailToCreateShortcut); |
| std::move(callback).Run(success); |
| } |
| |
| void WebAppShortcutManager::OnShortcutsDeleted(const AppId& app_id, |
| DeleteShortcutsCallback callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::move(callback).Run(success); |
| } |
| |
| void WebAppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts( |
| bool add_to_desktop, |
| CreateShortcutsCallback callback, |
| std::unique_ptr<ShortcutInfo> info) { |
| if (suppress_shortcuts_for_testing_) { |
| std::move(callback).Run(/*shortcut_created=*/true); |
| return; |
| } |
| |
| if (info == nullptr) { |
| std::move(callback).Run(/*shortcut_created=*/false); |
| return; |
| } |
| |
| base::FilePath shortcut_data_dir = internals::GetShortcutDataDir(*info); |
| |
| ShortcutLocations locations; |
| locations.on_desktop = add_to_desktop; |
| locations.applications_menu_location = APP_MENU_LOCATION_SUBDIR_CHROMEAPPS; |
| |
| internals::ScheduleCreatePlatformShortcuts( |
| std::move(shortcut_data_dir), locations, SHORTCUT_CREATION_BY_USER, |
| std::move(info), std::move(callback)); |
| } |
| |
| void WebAppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu( |
| const AppId& app_id, |
| RegisterShortcutsMenuCallback callback, |
| ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps) { |
| std::vector<WebApplicationShortcutsMenuItemInfo> shortcuts_menu_item_infos = |
| registrar_->GetAppShortcutsMenuItemInfos(app_id); |
| if (!shortcuts_menu_item_infos.empty()) { |
| RegisterShortcutsMenuWithOs(app_id, shortcuts_menu_item_infos, |
| shortcuts_menu_icon_bitmaps); |
| } |
| |
| std::move(callback).Run(/*shortcuts_menu_registered=*/true); |
| } |
| |
| void WebAppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts( |
| std::u16string old_name, |
| std::unique_ptr<ShortcutInfo> shortcut_info) { |
| if (GetShortcutUpdateCallbackForTesting()) |
| std::move(GetShortcutUpdateCallbackForTesting()).Run(shortcut_info.get()); |
| |
| if (suppress_shortcuts_for_testing_ || !shortcut_info) |
| return; |
| |
| base::FilePath shortcut_data_dir = |
| internals::GetShortcutDataDir(*shortcut_info); |
| internals::PostShortcutIOTask( |
| base::BindOnce(&internals::UpdatePlatformShortcuts, |
| std::move(shortcut_data_dir), std::move(old_name)), |
| std::move(shortcut_info)); |
| } |
| |
| std::unique_ptr<ShortcutInfo> WebAppShortcutManager::BuildShortcutInfo( |
| const AppId& app_id) { |
| const WebApp* app = registrar_->GetAppById(app_id); |
| DCHECK(app); |
| return BuildShortcutInfoForWebApp(app); |
| } |
| |
| void WebAppShortcutManager::GetShortcutInfoForApp( |
| const AppId& app_id, |
| GetShortcutInfoCallback callback) { |
| const WebApp* app = registrar_->GetAppById(app_id); |
| |
| // app could be nullptr if registry profile is being deleted. |
| if (!app) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| // Build a common intersection between desired and downloaded icons. |
| auto icon_sizes_in_px = base::STLSetIntersection<std::vector<SquareSizePx>>( |
| app->downloaded_icon_sizes(IconPurpose::ANY), |
| GetDesiredIconSizesForShortcut()); |
| |
| DCHECK(icon_manager_); |
| if (!icon_sizes_in_px.empty()) { |
| icon_manager_->ReadIcons(app_id, IconPurpose::ANY, icon_sizes_in_px, |
| base::BindOnce(&WebAppShortcutManager::OnIconsRead, |
| weak_ptr_factory_.GetWeakPtr(), |
| app_id, std::move(callback))); |
| return; |
| } |
| |
| // If there is no single icon at the desired sizes, we will resize what we can |
| // get. |
| SquareSizePx desired_icon_size = GetDesiredIconSizesForShortcut().back(); |
| |
| icon_manager_->ReadIconAndResize( |
| app_id, IconPurpose::ANY, desired_icon_size, |
| base::BindOnce(&WebAppShortcutManager::OnIconsRead, |
| weak_ptr_factory_.GetWeakPtr(), app_id, |
| std::move(callback))); |
| } |
| |
| void WebAppShortcutManager::OnIconsRead( |
| const AppId& app_id, |
| GetShortcutInfoCallback callback, |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps) { |
| // |icon_bitmaps| can be empty here if no icon found. |
| const WebApp* app = registrar_->GetAppById(app_id); |
| if (!app) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| gfx::ImageFamily image_family; |
| for (auto& size_and_bitmap : icon_bitmaps) { |
| image_family.Add(gfx::ImageSkia( |
| gfx::ImageSkiaRep(size_and_bitmap.second, /*scale=*/0.0f))); |
| } |
| |
| // If the image failed to load, use the standard application icon. |
| if (image_family.empty()) { |
| SquareSizePx icon_size_in_px = GetDesiredIconSizesForShortcut().back(); |
| gfx::ImageSkia image_skia = CreateDefaultApplicationIcon(icon_size_in_px); |
| image_family.Add(gfx::Image(image_skia)); |
| } |
| |
| std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfoForWebApp(app); |
| shortcut_info->favicon = std::move(image_family); |
| |
| std::move(callback).Run(std::move(shortcut_info)); |
| } |
| |
| std::unique_ptr<ShortcutInfo> WebAppShortcutManager::BuildShortcutInfoForWebApp( |
| const WebApp* app) { |
| auto shortcut_info = std::make_unique<ShortcutInfo>(); |
| |
| shortcut_info->extension_id = app->app_id(); |
| shortcut_info->url = app->start_url(); |
| shortcut_info->title = base::UTF8ToUTF16(app->name()); |
| shortcut_info->description = base::UTF8ToUTF16(app->description()); |
| shortcut_info->profile_path = profile_->GetPath(); |
| shortcut_info->profile_name = |
| profile_->GetPrefs()->GetString(prefs::kProfileName); |
| shortcut_info->is_multi_profile = true; |
| |
| if (const apps::FileHandlers* file_handlers = |
| file_handler_manager_->GetEnabledFileHandlers(app->app_id())) { |
| shortcut_info->file_handler_extensions = |
| GetFileExtensionsFromFileHandlers(*file_handlers); |
| shortcut_info->file_handler_mime_types = |
| GetMimeTypesFromFileHandlers(*file_handlers); |
| } |
| |
| std::vector<apps::ProtocolHandlerInfo> protocol_handlers = |
| protocol_handler_manager_->GetAppProtocolHandlerInfos(app->app_id()); |
| for (const auto& protocol_handler : protocol_handlers) { |
| if (!protocol_handler.protocol.empty()) { |
| shortcut_info->protocol_handlers.emplace(protocol_handler.protocol); |
| } |
| } |
| |
| #if defined(OS_LINUX) |
| const std::vector<WebApplicationShortcutsMenuItemInfo>& |
| shortcuts_menu_item_infos = app->shortcuts_menu_item_infos(); |
| int num_entries = std::min(static_cast<int>(shortcuts_menu_item_infos.size()), |
| kMaxApplicationDockMenuItems); |
| for (int i = 0; i < num_entries; i++) { |
| const auto& shortcuts_menu_item_info = shortcuts_menu_item_infos[i]; |
| if (!shortcuts_menu_item_info.name.empty() && |
| !shortcuts_menu_item_info.url.is_empty()) { |
| // Generates ID from the name by replacing all characters that are not |
| // numbers, letters, or '-' with '-'. |
| std::string id = base::UTF16ToUTF8(shortcuts_menu_item_info.name); |
| RE2::GlobalReplace(&id, "[^a-zA-Z0-9\\-]", "-"); |
| shortcut_info->actions.emplace( |
| id, base::UTF16ToUTF8(shortcuts_menu_item_info.name), |
| shortcuts_menu_item_info.url); |
| } |
| } |
| #endif // defined(OS_LINUX) |
| |
| return shortcut_info; |
| } |
| |
| } // namespace web_app |