blob: d90fb9198c27b3be02a92f96cfedd794306f1d11 [file] [log] [blame]
// 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_