| // 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/services/app_service/public/cpp/icon_coalescer.h" |
| |
| #include <iterator> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| |
| namespace apps { |
| |
| // scoped_refptr<RefCountedReleaser> converts a |
| // std::unique_ptr<IconLoader::Releaser> to a ref-counted pointer. |
| class IconCoalescer::RefCountedReleaser |
| : public base::RefCounted<RefCountedReleaser> { |
| public: |
| explicit RefCountedReleaser(std::unique_ptr<IconLoader::Releaser> releaser); |
| |
| private: |
| friend class base::RefCounted<RefCountedReleaser>; |
| |
| virtual ~RefCountedReleaser(); |
| |
| std::unique_ptr<IconLoader::Releaser> releaser_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RefCountedReleaser); |
| }; |
| |
| IconCoalescer::RefCountedReleaser::RefCountedReleaser( |
| std::unique_ptr<IconLoader::Releaser> releaser) |
| : releaser_(std::move(releaser)) {} |
| |
| IconCoalescer::RefCountedReleaser::~RefCountedReleaser() = default; |
| |
| IconCoalescer::IconCoalescer(IconLoader* wrapped_loader) |
| : wrapped_loader_(wrapped_loader), next_sequence_number_(0) {} |
| |
| IconCoalescer::~IconCoalescer() = default; |
| |
| apps::mojom::IconKeyPtr IconCoalescer::GetIconKey(const std::string& app_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return wrapped_loader_ ? wrapped_loader_->GetIconKey(app_id) |
| : apps::mojom::IconKey::New(); |
| } |
| |
| std::unique_ptr<IconLoader::Releaser> IconCoalescer::LoadIconFromIconKey( |
| apps::mojom::AppType app_type, |
| const std::string& app_id, |
| apps::mojom::IconKeyPtr icon_key, |
| apps::mojom::IconCompression icon_compression, |
| int32_t size_hint_in_dip, |
| bool allow_placeholder_icon, |
| apps::mojom::Publisher::LoadIconCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!wrapped_loader_) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return nullptr; |
| } |
| |
| if (icon_compression != apps::mojom::IconCompression::kUncompressed) { |
| return wrapped_loader_->LoadIconFromIconKey( |
| app_type, app_id, std::move(icon_key), icon_compression, |
| size_hint_in_dip, allow_placeholder_icon, std::move(callback)); |
| } |
| |
| scoped_refptr<RefCountedReleaser> shared_releaser; |
| IconLoader::Key key(app_type, app_id, icon_key, icon_compression, |
| size_hint_in_dip, allow_placeholder_icon); |
| |
| auto iter = non_immediate_requests_.find(key); |
| if (iter != non_immediate_requests_.end()) { |
| // Coalesce this request with an in-flight one. |
| // |
| // |iter->second| is a CallbackAndReleaser. |iter->second.second| is a |
| // scoped_refptr<RefCountedReleaser>. |
| shared_releaser = iter->second.second; |
| } else { |
| // There is no in-flight request to coalesce with. Instead, forward on the |
| // request to the wrapped IconLoader. |
| // |
| // Calling the |wrapped_loader_|'s LoadIconFromIconKey implementation might |
| // invoke the passed OnceCallback (binding this class' OnLoadIcon method) |
| // immediately (now), or at a later time. In both cases, we have to invoke |
| // (now or later) the |callback| that was passed to this function. |
| // |
| // If it's later, then we stash |callback| in |non_immediate_requests_|, |
| // and look up that same |non_immediate_requests_| during OnLoadIcon. |
| // |
| // If it's now, then inserting into the |non_immediate_requests_| would be |
| // tricky, as we'd have to then unstash the |callback| out of the |
| // |non_immediate_requests_| (recall that a OnceCallback can be std::move'd |
| // but not copied), but there are potentially multiple entries with the |
| // same key, and any multimap iterator might be invalidated if calling into |
| // the |wrapped_loader_| caused other code to call back into this |
| // IconCoalescer and mutate that multimap. |
| // |
| // Instead, |possibly_immediate_requests_| and |immediate_responses_| keeps |
| // track of now vs later. |
| // |
| // If it's now (if OnLoadIcon is called when the current |seq_num| is in |
| // |possibly_immediate_requests_|), then OnLoadIcon will populate |
| // |immediate_responses_| with that |seq_num|. We then run |callback| now, |
| // right after |wrapped_loader_->LoadIconFromIconKey| returns. |
| // |
| // Otherwise we have asynchronously dispatched the underlying icon loading |
| // request, so store |callback| in |non_immediate_requests_| to be run |
| // later, when the asynchronous request resolves. |
| uint64_t seq_num = next_sequence_number_++; |
| possibly_immediate_requests_.insert(seq_num); |
| |
| std::unique_ptr<IconLoader::Releaser> unique_releaser = |
| wrapped_loader_->LoadIconFromIconKey( |
| app_type, app_id, std::move(icon_key), icon_compression, |
| size_hint_in_dip, allow_placeholder_icon, |
| base::BindOnce(&IconCoalescer::OnLoadIcon, |
| weak_ptr_factory_.GetWeakPtr(), key, seq_num)); |
| |
| possibly_immediate_requests_.erase(seq_num); |
| |
| auto iv_iter = immediate_responses_.find(seq_num); |
| if (iv_iter != immediate_responses_.end()) { |
| apps::mojom::IconValuePtr iv = std::move(iv_iter->second); |
| immediate_responses_.erase(iv_iter); |
| std::move(callback).Run(std::move(iv)); |
| return unique_releaser; |
| } |
| |
| shared_releaser = |
| base::MakeRefCounted<RefCountedReleaser>(std::move(unique_releaser)); |
| } |
| |
| non_immediate_requests_.insert(std::make_pair( |
| key, std::make_pair(std::move(callback), shared_releaser))); |
| |
| return std::make_unique<IconLoader::Releaser>( |
| nullptr, |
| // The DoNothing callback does nothiing explicitly, but after it runs, it |
| // implicitly decrements the scoped_refptr's shared reference count, and |
| // therefore possibly deletes the underlying IconLoader::Releaser. |
| base::BindOnce(base::DoNothing::Once<scoped_refptr<RefCountedReleaser>>(), |
| std::move(shared_releaser))); |
| } |
| |
| void IconCoalescer::OnLoadIcon(IconLoader::Key key, |
| uint64_t sequence_number, |
| apps::mojom::IconValuePtr icon_value) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (possibly_immediate_requests_.find(sequence_number) != |
| possibly_immediate_requests_.end()) { |
| immediate_responses_.insert( |
| std::make_pair(sequence_number, std::move(icon_value))); |
| return; |
| } |
| |
| auto range = non_immediate_requests_.equal_range(key); |
| auto count = std::distance(range.first, range.second); |
| if (count <= 0) { |
| NOTREACHED(); |
| return; |
| } |
| |
| // Optimize / simplify the common case. |
| if (count == 1) { |
| CallbackAndReleaser callback_and_releaser = std::move(range.first->second); |
| non_immediate_requests_.erase(range.first, range.second); |
| std::move(callback_and_releaser.first).Run(std::move(icon_value)); |
| return; |
| } |
| |
| // Run every callback in |range|. This is subtle, because an arbitrary |
| // callback could invoke further methods on |this|, which could mutate |
| // |non_immediate_requests_|, invalidating |range|'s iterators. |
| // |
| // Thus, we first gather the callbacks, then erase the |range|, then run the |
| // callbacks. |
| // |
| // We still run the callbacks, synchronously, instead of posting them on a |
| // task runner to run later, asynchronously, even though using a task runner |
| // could avoid having to separate gathering and running the callbacks. |
| // Synchronous invocation keep the call stack's "how did I get here" |
| // information, which is useful when debugging. |
| |
| std::vector<apps::mojom::Publisher::LoadIconCallback> callbacks; |
| callbacks.reserve(count); |
| for (auto iter = range.first; iter != range.second; ++iter) { |
| // |iter->second| is a CallbackAndReleaser. |iter->second.first| is a |
| // LoadIconCallback. |
| callbacks.push_back(std::move(iter->second.first)); |
| } |
| |
| non_immediate_requests_.erase(range.first, range.second); |
| |
| for (auto& callback : callbacks) { |
| apps::mojom::IconValuePtr iv; |
| if (--count == 0) { |
| iv = std::move(icon_value); |
| } else { |
| iv = apps::mojom::IconValue::New(); |
| iv->icon_compression = apps::mojom::IconCompression::kUncompressed; |
| iv->uncompressed = icon_value->uncompressed; |
| iv->is_placeholder_icon = icon_value->is_placeholder_icon; |
| } |
| std::move(callback).Run(std::move(iv)); |
| } |
| } |
| |
| } // namespace apps |