| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/lacros/lacros_file_system_provider.h" |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/ranges/algorithm.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h" |
| #include "chromeos/lacros/lacros_service.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_event_histogram_value.h" |
| #include "extensions/browser/image_loader.h" |
| #include "extensions/browser/unloaded_extension_reason.h" |
| #include "extensions/common/extension_icon_set.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/manifest_handlers/icons_handler.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| namespace { |
| |
| // TODO(b/266519645): scope object lifetime to the lifetime of |
| // `LacrosFileSystemProvider`. |
| class IconLoadTask : public base::RefCounted<IconLoadTask> { |
| public: |
| using Callback = |
| base::OnceCallback<void(const gfx::Image&, const gfx::Image&)>; |
| IconLoadTask(content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| Callback callback) |
| : callback_(std::move(callback)) { |
| LoadExtensionIcon(browser_context, extension, icon16x16_, 16); |
| LoadExtensionIcon(browser_context, extension, icon32x32_, 32); |
| } |
| // Loads an icon of a single size. |
| void LoadExtensionIcon(content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| absl::optional<gfx::Image>& target, |
| int size) { |
| extensions::ExtensionResource icon = extensions::IconsInfo::GetIconResource( |
| extension, size, ExtensionIconSet::MatchType::MATCH_BIGGER); |
| |
| extensions::ImageLoader::Get(browser_context) |
| ->LoadImageAsync(extension, icon, gfx::Size(size, size), |
| base::BindOnce(&IconLoadTask::OnIconLoaded, this, |
| std::ref(target))); |
| } |
| |
| private: |
| ~IconLoadTask() = default; |
| friend base::RefCounted<IconLoadTask>; |
| |
| void OnIconLoaded(absl::optional<gfx::Image>& target, |
| const gfx::Image& icon) { |
| target = icon; |
| if (icon16x16_ && icon32x32_) { |
| std::move(callback_).Run(*icon16x16_, *icon32x32_); |
| } |
| } |
| |
| Callback callback_; |
| absl::optional<gfx::Image> icon16x16_; |
| absl::optional<gfx::Image> icon32x32_; |
| }; |
| |
| void NotifyAshExtensionLoaded( |
| const std::string& id, |
| const std::string& name, |
| const extensions::FileSystemProviderCapabilities& capabilities, |
| const gfx::Image& icon16x16, |
| const gfx::Image& icon32x32) { |
| chromeos::LacrosService* service = chromeos::LacrosService::Get(); |
| int fsp_service_version = service->GetInterfaceVersion( |
| crosapi::mojom::FileSystemProviderService::Uuid_); |
| if (fsp_service_version < |
| int{crosapi::mojom::FileSystemProviderService::MethodMinVersions:: |
| kExtensionLoadedDeprecatedMinVersion}) { |
| return; |
| } |
| |
| crosapi::mojom::FileSystemSource source; |
| switch (capabilities.source()) { |
| case extensions::FileSystemProviderSource::SOURCE_FILE: |
| source = crosapi::mojom::FileSystemSource::kFile; |
| break; |
| case extensions::FileSystemProviderSource::SOURCE_NETWORK: |
| source = crosapi::mojom::FileSystemSource::kNetwork; |
| break; |
| case extensions::FileSystemProviderSource::SOURCE_DEVICE: |
| source = crosapi::mojom::FileSystemSource::kDevice; |
| break; |
| } |
| |
| auto& fsp_service = |
| service->GetRemote<crosapi::mojom::FileSystemProviderService>(); |
| if (fsp_service_version < |
| int{crosapi::mojom::FileSystemProviderService::MethodMinVersions:: |
| kExtensionLoadedMinVersion}) { |
| fsp_service->ExtensionLoadedDeprecated( |
| capabilities.configurable(), capabilities.watchable(), |
| capabilities.multiple_mounts(), source, name, id); |
| return; |
| } |
| |
| fsp_service->ExtensionLoaded( |
| capabilities.configurable(), capabilities.watchable(), |
| capabilities.multiple_mounts(), source, name, id, icon16x16.AsImageSkia(), |
| icon32x32.AsImageSkia()); |
| } |
| |
| // Returns the single main profile, or nullptr if none is found. |
| Profile* GetMainProfile() { |
| auto profiles = g_browser_process->profile_manager()->GetLoadedProfiles(); |
| const auto main_it = base::ranges::find_if(profiles, &Profile::IsMainProfile); |
| if (main_it == profiles.end()) |
| return nullptr; |
| return *main_it; |
| } |
| } // namespace |
| |
| LacrosFileSystemProvider::LacrosFileSystemProvider() : receiver_{this} { |
| chromeos::LacrosService* service = chromeos::LacrosService::Get(); |
| if (!service->IsAvailable<crosapi::mojom::FileSystemProviderService>()) |
| return; |
| service->GetRemote<crosapi::mojom::FileSystemProviderService>() |
| ->RegisterFileSystemProvider(receiver_.BindNewPipeAndPassRemote()); |
| |
| Profile* main_profile = GetMainProfile(); |
| if (main_profile) { |
| extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(main_profile); |
| extension_observation_.Observe(registry); |
| |
| // Initial conditions |
| for (const scoped_refptr<const extensions::Extension> extension : |
| registry->enabled_extensions()) { |
| OnExtensionLoaded(main_profile, extension.get()); |
| } |
| } |
| } |
| LacrosFileSystemProvider::~LacrosFileSystemProvider() = default; |
| |
| void LacrosFileSystemProvider::DeprecatedDeprecatedForwardOperation( |
| const std::string& provider, |
| int32_t histogram_value, |
| const std::string& event_name, |
| std::vector<base::Value> args) { |
| DeprecatedForwardOperation(provider, histogram_value, event_name, |
| std::move(args), base::DoNothing()); |
| } |
| |
| void LacrosFileSystemProvider::DeprecatedForwardOperation( |
| const std::string& provider, |
| int32_t histogram_value, |
| const std::string& event_name, |
| std::vector<base::Value> args, |
| ForwardOperationCallback callback) { |
| base::Value::List list; |
| for (auto& value : args) { |
| list.Append(std::move(value)); |
| } |
| ForwardOperation(provider, histogram_value, event_name, std::move(list), |
| base::DoNothing()); |
| } |
| |
| void LacrosFileSystemProvider::ForwardOperation( |
| const std::string& provider, |
| int32_t histogram_value, |
| const std::string& event_name, |
| base::Value::List args, |
| ForwardOperationCallback callback) { |
| ForwardRequest( |
| provider, "", 0, histogram_value, event_name, std::move(args), |
| base::BindOnce( |
| [](ForwardOperationCallback callback, |
| crosapi::mojom::FSPForwardResult result) { |
| std::move(callback).Run(result != |
| crosapi::mojom::FSPForwardResult::kSuccess); |
| }, |
| std::move(callback))); |
| } |
| |
| void LacrosFileSystemProvider::ForwardRequest( |
| const std::string& provider, |
| const absl::optional<std::string>& file_system_id, |
| int64_t request_id, |
| int32_t histogram_value, |
| const std::string& event_name, |
| base::Value::List args, |
| ForwardRequestCallback callback) { |
| Profile* main_profile = GetMainProfile(); |
| if (!main_profile) { |
| LOG(ERROR) << "Could not get main profile"; |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kInternalError); |
| return; |
| } |
| |
| extensions::EventRouter* router = extensions::EventRouter::Get(main_profile); |
| if (!router) { |
| LOG(ERROR) << "Could not get event router"; |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kInternalError); |
| return; |
| } |
| |
| if (!router->ExtensionHasEventListener(provider, event_name)) { |
| LOG(ERROR) << "Could not get event listener"; |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kNoListener); |
| return; |
| } |
| |
| // Conversions are safe since the enum is stable. See documentation. |
| int32_t lowest_valid_enum = |
| static_cast<int32_t>(extensions::events::HistogramValue::UNKNOWN); |
| int32_t highest_valid_enum = |
| static_cast<int32_t>(extensions::events::HistogramValue::ENUM_BOUNDARY) - |
| 1; |
| if (histogram_value < lowest_valid_enum || |
| histogram_value > highest_valid_enum) { |
| LOG(ERROR) << "Invalid histogram"; |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kInternalError); |
| return; |
| } |
| extensions::events::HistogramValue histogram = |
| static_cast<extensions::events::HistogramValue>(histogram_value); |
| |
| if (request_id > 0) { |
| // request_id will be > 0 if Ash calls ForwardRequest (crosapi with |
| // request_id as an argument). |
| auto* sw_lifetime_manager = |
| extensions::file_system_provider::ServiceWorkerLifetimeManager::Get( |
| main_profile); |
| if (!sw_lifetime_manager) { |
| LOG(ERROR) << "Could not get service worker lifetime manager"; |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kInternalError); |
| return; |
| } |
| auto event = std::make_unique<extensions::Event>(histogram, event_name, |
| std::move(args)); |
| extensions::file_system_provider::RequestKey request_key{ |
| provider, file_system_id.value_or(""), request_id}; |
| event->did_dispatch_callback = |
| sw_lifetime_manager->CreateDispatchCallbackForRequest(request_key); |
| router->DispatchEventToExtension(provider, std::move(event)); |
| sw_lifetime_manager->StartRequest(request_key); |
| } else { |
| // request_id is 0 if Ash calls ForwardOperation (older crosapi without |
| // request_id). |
| auto event = std::make_unique<extensions::Event>(histogram, event_name, |
| std::move(args)); |
| router->DispatchEventToExtension(provider, std::move(event)); |
| } |
| std::move(callback).Run(crosapi::mojom::FSPForwardResult::kSuccess); |
| } |
| |
| void LacrosFileSystemProvider::CancelRequest( |
| const std::string& provider, |
| const absl::optional<std::string>& file_system_id, |
| int64_t request_id) { |
| Profile* main_profile = GetMainProfile(); |
| if (!main_profile) { |
| LOG(ERROR) << "Could not get main profile"; |
| return; |
| } |
| auto* sw_lifetime_manager = |
| extensions::file_system_provider::ServiceWorkerLifetimeManager::Get( |
| main_profile); |
| sw_lifetime_manager->FinishRequest( |
| extensions::file_system_provider::RequestKey{ |
| provider, file_system_id.value_or(""), request_id}); |
| } |
| |
| void LacrosFileSystemProvider::OnExtensionLoaded( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension) { |
| if (!extension->permissions_data()->HasAPIPermission( |
| extensions::mojom::APIPermissionID::kFileSystemProvider)) { |
| return; |
| } |
| const extensions::FileSystemProviderCapabilities* const capabilities = |
| extensions::FileSystemProviderCapabilities::Get(extension); |
| if (!capabilities) { |
| return; |
| } |
| |
| base::MakeRefCounted<IconLoadTask>( |
| browser_context, extension, |
| base::BindOnce(&NotifyAshExtensionLoaded, extension->id(), |
| extension->name(), *capabilities)); |
| } |
| |
| void LacrosFileSystemProvider::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UnloadedExtensionReason reason) { |
| chromeos::LacrosService* service = chromeos::LacrosService::Get(); |
| if (service->GetInterfaceVersion( |
| crosapi::mojom::FileSystemProviderService::Uuid_) < |
| int{crosapi::mojom::FileSystemProviderService::MethodMinVersions:: |
| kExtensionUnloadedMinVersion}) { |
| return; |
| } |
| service->GetRemote<crosapi::mojom::FileSystemProviderService>() |
| ->ExtensionUnloaded( |
| extension->id(), |
| reason == extensions::UnloadedExtensionReason::PROFILE_SHUTDOWN); |
| } |
| |
| void LacrosFileSystemProvider::OnShutdown( |
| extensions::ExtensionRegistry* registry) { |
| extension_observation_.Reset(); |
| } |