| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UPDATE_SERVICE_H_ |
| #define CHROME_BROWSER_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UPDATE_SERVICE_H_ |
| |
| #include <map> |
| #include <vector> |
| |
| #include "base/dcheck_is_on.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/scoped_observation.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/download/bubble/download_display_controller.h" |
| #include "chrome/browser/download/download_ui_model.h" |
| #include "chrome/browser/web_applications/web_app_id.h" |
| #include "components/download/content/public/all_download_item_notifier.h" |
| #include "components/keyed_service/core/keyed_service.h" |
| #include "components/offline_items_collection/core/offline_content_provider.h" |
| #include "components/offline_items_collection/core/offline_item.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| class Profile; |
| |
| namespace content { |
| class DownloadManager; |
| } // namespace content |
| |
| namespace download { |
| class DownloadItem; |
| } // namespace download |
| |
| // Caches download items and offline items in sorted order, so that UI updates |
| // can be processed more quickly without fetching and sorting all items every |
| // time. Passes notifications on to the window-level UI controllers. |
| class DownloadBubbleUpdateService |
| : public KeyedService, |
| public download::AllDownloadItemNotifier::Observer, |
| public offline_items_collection::OfflineContentProvider::Observer { |
| public: |
| // Defines sort priority for items. |
| struct ItemSortKey { |
| enum State { |
| kInProgressActive = 0, |
| kInProgressPaused = 1, |
| kNotInProgress = 2, |
| }; |
| |
| bool operator<(const ItemSortKey& other) const; |
| bool operator==(const ItemSortKey& other) const; |
| bool operator!=(const ItemSortKey& other) const; |
| bool operator>(const ItemSortKey& other) const; |
| |
| // Active in-progress items come before paused items, which come before |
| // not-in-progress items. |
| State state; |
| // Within each state, items are sorted in reverse chronological order by |
| // start time (most recent first). |
| base::Time start_time; |
| }; |
| |
| explicit DownloadBubbleUpdateService(Profile* profile); |
| DownloadBubbleUpdateService(const DownloadBubbleUpdateService&) = delete; |
| DownloadBubbleUpdateService& operator=(const DownloadBubbleUpdateService&) = |
| delete; |
| |
| ~DownloadBubbleUpdateService() override; |
| |
| // Gets models for the top GetMaxNumItemsToShow() combined download items |
| // and offline items, in sorted order. If |web_app_id| is non-null, the |
| // results are limited to downloads initiated by the specified web app, |
| // otherwise the results are limited to downloads initiated by normal Chrome |
| // windows. May cause items to be pruned from the cache, if they have grown |
| // too old to be included. May trigger backfilling the caches, but does not |
| // wait for backfill results, unless |force_backfill_download_items| is true |
| // (in which case download items will be backfilled synchronously if |
| // necessary; offline items will not be backfilled synchronously). |models| is |
| // cleared. Returns whether results are complete. Results may not be complete |
| // if there might be more items to be returned after backfilling. Virtual for |
| // testing. |
| virtual bool GetAllModelsToDisplay( |
| std::vector<DownloadUIModel::DownloadUIModelPtr>& models, |
| const web_app::AppId* web_app_id, |
| bool force_backfill_download_items = false); |
| |
| // Returns information relevant to the display state of the download button. |
| // If |web_app_id| is non-null, the results are limited to downloads initiated |
| // by the specified web app, otherwise the results are limited to downloads |
| // initiated by normal Chrome windows. Does not prune the cache or backfill |
| // missing items. May be slightly inaccurate in edge cases. Virtual for |
| // testing. |
| virtual const DownloadDisplayController::AllDownloadUIModelsInfo& |
| GetAllModelsInfo(const web_app::AppId* web_app_id); |
| |
| // Computes progress info based on in-progress downloads. If |web_app_id| is |
| // non-null, the results are limited to downloads initiated by the specified |
| // web app, otherwise the results are limited to downloads initiated by normal |
| // Chrome windows. Does not prune the cache or backfill missing items, so the |
| // returned progress info may be slightly inaccurate in edge cases. This is |
| // ok, as it is only for the purpose of showing a progress ring around the |
| // icon, which is not precise anyway. Virtual for testing. |
| virtual DownloadDisplayController::ProgressInfo GetProgressInfo( |
| const web_app::AppId* web_app_id) const; |
| |
| // Notifies the appropriate browser windows that a download item was added. |
| void NotifyWindowsOfDownloadItemAdded(download::DownloadItem* item); |
| |
| // Initializes AllDownloadItemNotifier for the current profile, and |
| // initializes caches. This is called when the manager is ready, to signal |
| // that the DownloadBubbleUpdateService should begin tracking downloads. This |
| // starts initialization of both the download items and the offline items. |
| // Should only be called once. |
| void Initialize(content::DownloadManager* manager); |
| |
| // Initializes the AllDownloadItemNotifier for the original profile, if |
| // |profile_| is off the record. May trigger re-initialization of the download |
| // items cache. |
| void InitializeOriginalNotifier(content::DownloadManager* manager); |
| |
| // Get the DownloadManager that |download_item_notifier_| is listening to. |
| content::DownloadManager* GetDownloadManager(); |
| |
| // Virtual for testing. |
| virtual bool IsInitialized() const; |
| |
| // KeyedService: |
| void Shutdown() override; |
| |
| bool IsShutDown() const; |
| |
| // download::AllDownloadItemNotifier::Observer: |
| void OnDownloadCreated(content::DownloadManager* manager, |
| download::DownloadItem* item) override; |
| void OnDownloadUpdated(content::DownloadManager* manager, |
| download::DownloadItem* item) override; |
| void OnDownloadRemoved(content::DownloadManager* manager, |
| download::DownloadItem* item) override; |
| void OnManagerGoingDown(content::DownloadManager* manager) override; |
| |
| // offline_items_collection::OfflineContentProvider::Observer: |
| void OnItemsAdded( |
| const offline_items_collection::OfflineContentProvider::OfflineItemList& |
| items) override; |
| void OnItemRemoved(const offline_items_collection::ContentId& id) override; |
| void OnItemUpdated( |
| const offline_items_collection::OfflineItem& item, |
| const absl::optional<offline_items_collection::UpdateDelta>& update_delta) |
| override; |
| void OnContentProviderGoingDown() override; |
| |
| OfflineItemModelManager* GetOfflineManager() const; |
| |
| bool IsProfileOtr() const; |
| |
| // Returns the max number of combined download items and offline items that |
| // will be returned from GetAllModelsToDisplay(). Applies to each |
| // CacheManager. |
| size_t GetMaxNumItemsToShow() const; |
| |
| // Returns the max number of items (of each type) to cache. This is slightly |
| // more than the max number of items to show. Applies to each CacheManager. |
| size_t GetNumItemsToCache() const; |
| |
| // Gets all items from the DownloadManager/ContentProvider, finds the top |
| // items that sort at or after |last_key| and adds them to the cache such that |
| // the total number of items does not exceed the max. The Start*() versions |
| // just post a task to kick off backfilling while the other two perform the |
| // backfilling synchronously. Note that it is ok if other additions/deletions |
| // happen while the backfill task is queued. If an item is inserted before |
| // last_key then it would have been there anyway. If an item is inserted |
| // after last_key, it is the same as if it were added during backfilling. If |
| // an item is removed before last_key, then there is just more space to |
| // backfill. |
| void StartBackfillDownloadItems(const ItemSortKey& last_key); |
| void BackfillDownloadItems(const ItemSortKey& last_key); |
| void StartBackfillOfflineItems(const ItemSortKey& last_key); |
| void BackfillOfflineItems( |
| const ItemSortKey& last_key, |
| const std::vector<offline_items_collection::OfflineItem>& all_items); |
| |
| // Logic in CacheManager assumes the max is at least 2. |
| void set_max_num_items_to_show_for_testing(size_t max) { |
| max_num_items_to_show_for_testing_ = max; |
| } |
| |
| void set_extra_items_to_cache_for_testing(size_t items) { |
| extra_items_to_cache_for_testing_ = items; |
| } |
| |
| download::AllDownloadItemNotifier& download_item_notifier_for_testing() { |
| return *download_item_notifier_; |
| } |
| |
| download::AllDownloadItemNotifier& |
| original_download_item_notifier_for_testing() { |
| return *original_download_item_notifier_; |
| } |
| |
| base::WeakPtr<DownloadBubbleUpdateService> GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| // Encapsulates the caching functionality of DownloadBubbleUpdateService. |
| // Holds two caches, one for DownloadItems and one for OfflineItems, and their |
| // associated indexes and aggregate info (AllDownloadUIModelsInfo). Represents |
| // one "namespace" of items for the download bubble, corresponding to either a |
| // single web app or all the regular (non-app) Chrome browsers. |
| class CacheManager { |
| public: |
| template <typename Item> |
| using SortedItems = std::multimap<ItemSortKey, Item>; |
| template <typename Id, typename Item> |
| using IterMap = std::map<Id, typename SortedItems<Item>::iterator>; |
| |
| using SortedDownloadItems = SortedItems<download::DownloadItem*>; |
| using SortedOfflineItems = |
| SortedItems<offline_items_collection::OfflineItem>; |
| using DownloadItemIterMap = IterMap<std::string, download::DownloadItem*>; |
| using OfflineItemIterMap = IterMap<offline_items_collection::ContentId, |
| offline_items_collection::OfflineItem>; |
| |
| explicit CacheManager(DownloadBubbleUpdateService* update_service); |
| ~CacheManager(); |
| |
| CacheManager(const CacheManager&) = delete; |
| CacheManager& operator=(const CacheManager&) = delete; |
| |
| // Whether the cache is at its max allowed capacity. |
| bool IsDownloadItemCacheAtMax() const; |
| bool IsOfflineItemCacheAtMax() const; |
| |
| // See comments on the public DownloadBubbleUpdateService methods of the |
| // same name. |
| bool GetAllModelsToDisplay( |
| std::vector<DownloadUIModel::DownloadUIModelPtr>& models, |
| bool force_backfill_download_items = false); |
| const DownloadDisplayController::AllDownloadUIModelsInfo& GetAllModelsInfo() |
| const; |
| DownloadDisplayController::ProgressInfo GetProgressInfo() const; |
| |
| // Adds an item to the cache if it is recent enough and meets other criteria |
| // for showing in the bubble. If adding an item makes the map size exceed |
| // the maximum, this removes excess items from the end of the map. Returns |
| // whether the item was stored as the last item in the map. If |item| was |
| // already in the cache, this does nothing. |is_new| is whether the item is |
| // a newly added item (as opposed to an updated one). May mark the item |
| // model as not-actioned-on if the item is new. |
| bool MaybeAddDownloadItemToCache(download::DownloadItem* item, bool is_new); |
| bool MaybeAddOfflineItemToCache( |
| const offline_items_collection::OfflineItem& item, |
| bool is_new); |
| |
| // Updates an item by removing it and reinserting it in the cache. May |
| // kick off backfilling of the cache. |
| void OnDownloadItemUpdated(download::DownloadItem* item); |
| void OnOfflineItemUpdated( |
| const offline_items_collection::OfflineItem& item); |
| |
| // Removes an item from the cache. May kick off backfilling of the cache. |
| void OnDownloadItemRemoved(download::DownloadItem* item); |
| void OnOfflineItemRemoved(const offline_items_collection::ContentId& id); |
| |
| // Removes an item from the maps. Note: If the cache size was already at the |
| // limit, and removing an item brings it under that limit, we must then get |
| // all items in order to backfill the newly created space. (See |
| // Backfill*Items() below.) Returns whether item was removed. If |item| is |
| // not already in the cache, this does nothing. |
| bool RemoveDownloadItemFromCache(download::DownloadItem* item); |
| bool RemoveOfflineItemFromCache( |
| const offline_items_collection::ContentId& id); |
| |
| // Updates |all_models_info_| based on the current contents of the cache. |
| // This is kept updated as items are added or removed from the cache. |
| void UpdateAllModelsInfo(); |
| |
| // Clears the cache. |
| void DropAllDownloadItems(); |
| void DropAllOfflineItems(); |
| |
| private: |
| // Forwards to |update_service_|. |
| size_t GetMaxNumItemsToShow() const; |
| size_t GetNumItemsToCache() const; |
| |
| template <typename Id, typename Item> |
| bool AddItemToCacheImpl(Item item, |
| SortedItems<Item>& cache, |
| IterMap<Id, Item>& iter_map); |
| |
| template <typename Id, typename Item> |
| bool RemoveItemFromCacheImpl(const Id& id, |
| SortedItems<Item>& cache, |
| IterMap<Id, Item>& iter_map); |
| |
| // Removes item if we already have the iterator to it. Returns next |
| // iterator. |
| template <typename Id, typename Item> |
| typename SortedItems<Item>::iterator RemoveItemFromCacheByIter( |
| typename SortedItems<Item>::iterator iter, |
| SortedItems<Item>& cache, |
| IterMap<Id, Item>& iter_map); |
| |
| // Wraps an item into a DownloadUIModel and possibly adds it to |models|, if |
| // it is new enough (newer than |cutoff_time|) and meets other criteria. |
| // Returns whether model was eligible to be added, regardless of whether it |
| // was added (it may not have been added if |models| was at the size limit). |
| bool MaybeAddDownloadItemModel( |
| download::DownloadItem* item, |
| base::Time cutoff_time, |
| std::vector<DownloadUIModel::DownloadUIModelPtr>& models); |
| bool MaybeAddOfflineItemModel( |
| const offline_items_collection::OfflineItem& item, |
| base::Time cutoff_time, |
| std::vector<DownloadUIModel::DownloadUIModelPtr>& models); |
| |
| // Append newly backfilled download items to |models|. |last_key| is the |
| // last key that was processed before backfilling. May prune any expired |
| // items (i.e. items older than |cutoff_time|). |
| void AppendBackfilledDownloadItems( |
| const ItemSortKey& last_key, |
| base::Time cutoff_time, |
| std::vector<DownloadUIModel::DownloadUIModelPtr>& models); |
| |
| #if DCHECK_IS_ON() |
| // Checks that the cache data structures are consistent. |
| void ConsistencyCheckCaches() const; |
| |
| template <typename Id, typename Item> |
| void ConsistencyCheckImpl(const SortedItems<Item>& cache, |
| const IterMap<Id, Item>& iter_map) const; |
| #endif // DCHECK_IS_ON() |
| |
| // DownloadBubbleUpdateService that owns this. Never null. |
| const raw_ptr<DownloadBubbleUpdateService> update_service_ = nullptr; |
| |
| // Caches the current most-relevant items in sorted order. Size of each map |
| // will generally be limited to GetNumItemsToCache (except during addition |
| // of an item). Note: These are multimaps because, in theory, multiple items |
| // might have the same sort key. The cache manipulation logic in this class |
| // accounts for this by assuming that, if there's not enough space for all |
| // the items with the last key, then caching an arbitrary subset of them is |
| // fine. |
| SortedDownloadItems download_items_; |
| SortedOfflineItems offline_items_; |
| |
| // Holds iterators pointing into the above two maps, allowing lookup of an |
| // item by GUID or ContentId. |
| DownloadItemIterMap download_items_iter_map_; |
| OfflineItemIterMap offline_items_iter_map_; |
| |
| // Holds the latest info about all models, relevant to the display state of |
| // the download toolbar icon. |
| DownloadDisplayController::AllDownloadUIModelsInfo all_models_info_; |
| }; |
| |
| public: |
| // Convenience typedefs for brevity in the implementation file. |
| template <typename Id, typename Item> |
| using IterMap = CacheManager::IterMap<Id, Item>; |
| template <typename Item> |
| using SortedItems = CacheManager::SortedItems<Item>; |
| |
| // Checks whether |cache| is the main cache, used for CHECKs to ensure that |
| // offline items only go into the main cache. |
| bool IsMainCache(const CacheManager& cache) const; |
| |
| // Called when a download with an ephemeral warning should disappear from the |
| // download bubble. To enact the disappearance, the item is omitted when |
| // calculating `all_models_info_` and GetAllModelsToDisplay(). The item |
| // remains in the cache until pruned in GetAllModelsToDisplay(). This function |
| // handles the other part of that, which is updating `all_models_info_` and |
| // notifying the display controller (which then hides the toolbar button if no |
| // other downloads are displayed). |
| void OnEphemeralWarningExpired(const std::string& guid); |
| |
| private: |
| // Finds the appropriate CacheManager for a web app, creating one if it |
| // doesn't exist. |
| CacheManager& GetCacheForWebApp(const web_app::AppId& app_id); |
| // As above, but does not create one if it doesn't exist (in which case it |
| // returns nullptr). |
| const CacheManager* GetExistingCacheForWebApp( |
| const web_app::AppId& app_id) const; |
| |
| // Finds the appropriate CacheManager for a download item, creating one if it |
| // doesn't exist. |
| CacheManager& GetCacheForItem(download::DownloadItem* item); |
| |
| // Returns pointers to all CacheManagers this object owns, in no particular |
| // order. |
| std::vector<CacheManager*> GetAllCacheManagers(); |
| |
| // Populate the cache from items fetched from the download manager or |
| // offline content manager. |
| void InitializeDownloadItemsCache(); |
| void StartInitializeOfflineItemsCache(); |
| void InitializeOfflineItemsCache( |
| const std::vector<offline_items_collection::OfflineItem>& all_items); |
| |
| // Gets download items from profile and original profile. |
| std::vector<download::DownloadItem*> GetAllDownloadItems(); |
| |
| // Called when a crx download has waited out its 2 second delay. Adds the |
| // item to the cache if it's not already done, and notifies window-level |
| // controllers. |
| void OnDelayedCrxDownloadCreated(const std::string& guid); |
| |
| // Profile corresponding to this object. |
| const raw_ptr<Profile> profile_ = nullptr; |
| // Null if the profile is not OTR. |
| const raw_ptr<Profile> original_profile_ = nullptr; |
| |
| // Override for the number of combined items to return. |
| absl::optional<size_t> max_num_items_to_show_for_testing_; |
| // Override for the number of extra items to cache. |
| absl::optional<size_t> extra_items_to_cache_for_testing_; |
| |
| // Notifier for the current profile's DownloadManager. Null until initialized |
| // in Initialize(). |
| std::unique_ptr<download::AllDownloadItemNotifier> download_item_notifier_; |
| // Null if the profile is not OTR. Null until the original profile initiates |
| // a download. If the profile is OTR, this holds a notifier for the original |
| // profile. |
| std::unique_ptr<download::AllDownloadItemNotifier> |
| original_download_item_notifier_; |
| |
| bool offline_items_initialized_ = false; |
| // Holds functions queued up while offline items were being initialized. |
| std::vector<base::OnceClosure> offline_item_callbacks_; |
| |
| // Whether Shutdown() has been called, or the download manager or offline |
| // content provider have been shut down. |
| bool is_shut_down_ = false; |
| |
| // Set of GUIDs for extension/theme (crx) downloads that are pending notifying |
| // the UI. GUIDs are added here when the download begins, and are removed |
| // when the 2 second delay is up. |
| std::set<std::string> delayed_crx_guids_; |
| |
| // The cache for all regular Chrome windows. Note that offline items all go |
| // here, whereas download items may end up in other CacheManagers depending |
| // on whether they were downloaded by a web app. |
| CacheManager main_cache_; |
| |
| // A separate cache for each web app. |
| std::map<web_app::AppId, CacheManager> web_app_caches_; |
| |
| // Observes the offline content provider. |
| base::ScopedObservation< |
| offline_items_collection::OfflineContentProvider, |
| offline_items_collection::OfflineContentProvider::Observer> |
| offline_content_provider_observation_{this}; |
| |
| base::WeakPtrFactory<DownloadBubbleUpdateService> weak_factory_{this}; |
| }; |
| |
| #endif // CHROME_BROWSER_DOWNLOAD_BUBBLE_DOWNLOAD_BUBBLE_UPDATE_SERVICE_H_ |