Commit 2e4e0f37 authored by Collin Baker's avatar Collin Baker Committed by Chromium LUCI CQ

Reland "Replace ThumbnailImage::Observer with Subscriber"

This is a reland of e4a4b7a3

Fixes null dereference bug

Original change's description:
> Replace ThumbnailImage::Observer with Subscriber
>
> Previously, clients interested in seeing thumbnail updates had to
> derive from ThumbnailImage::Observer and register themselves with
> ThumbnailImage.
>
> Removing ThumbnailTabHelper and ThumbnailImage from the public API and
> instead exposing a global ThumbnailService is a design
> goal. ThumbnailImage::Observer makes this refactor difficult.
>
> This CL uses a callback-based subscriber model instead.
> base::Callbacks replace observer methods. Hints are supplied
> directly on the subscription object. The subscription models loosens
> the coupling between clients and ThumbnailImage.
>
> Clients subscribe by ThumbnailImage::Subscribe(). Subscription can
> later be pushed up easily to the ThumbnailService level by a method
> like ThumbnailService::Subscribe(content::WebContents*).
>
> Bug: 1090038
> Change-Id: I020d848e00331d5f5bbb562f0c53aec5f8ccbc62
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2593158
> Reviewed-by: Dana Fried <dfried@chromium.org>
> Reviewed-by: John Lee <johntlee@chromium.org>
> Commit-Queue: John Lee <johntlee@chromium.org>
> Auto-Submit: Collin Baker <collinbaker@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#837818}

Bug: 1090038
Change-Id: I3d93fc36d9725c088ba684188265df1ad1fd0905
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612046
Commit-Queue: Collin Baker <collinbaker@chromium.org>
Reviewed-by: default avatarJohn Lee <johntlee@chromium.org>
Reviewed-by: default avatarDana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842260}
parent c6e67658
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
#include "chrome/browser/ui/thumbnails/thumbnail_image.h" #include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include <algorithm>
#include <utility> #include <utility>
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
...@@ -15,15 +17,12 @@ ...@@ -15,15 +17,12 @@
#include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/skia_util.h" #include "ui/gfx/skia_util.h"
void ThumbnailImage::Observer::OnThumbnailImageAvailable( ThumbnailImage::Subscription::Subscription(
gfx::ImageSkia thumbnail_image) {} scoped_refptr<ThumbnailImage> thumbnail)
: thumbnail_(std::move(thumbnail)) {}
void ThumbnailImage::Observer::OnCompressedThumbnailDataAvailable( ThumbnailImage::Subscription::~Subscription() {
CompressedThumbnailData thumbnail_data) {} thumbnail_->HandleSubscriptionDestroyed(this);
base::Optional<gfx::Size> ThumbnailImage::Observer::GetThumbnailSizeHint()
const {
return base::nullopt;
} }
ThumbnailImage::Delegate::~Delegate() { ThumbnailImage::Delegate::~Delegate() {
...@@ -46,29 +45,17 @@ ThumbnailImage::~ThumbnailImage() { ...@@ -46,29 +45,17 @@ ThumbnailImage::~ThumbnailImage() {
delegate_->thumbnail_ = nullptr; delegate_->thumbnail_ = nullptr;
} }
void ThumbnailImage::AddObserver(Observer* observer) { std::unique_ptr<ThumbnailImage::Subscription> ThumbnailImage::Subscribe() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Use explicit new since Subscription constructor is private.
DCHECK(observer); auto subscription =
if (!observers_.HasObserver(observer)) { base::WrapUnique(new Subscription(base::WrapRefCounted(this)));
const bool is_first_observer = !observers_.might_have_observers(); subscribers_.insert(subscribers_.end(), subscription.get());
observers_.AddObserver(observer);
if (is_first_observer && delegate_)
delegate_->ThumbnailImageBeingObservedChanged(true);
}
}
void ThumbnailImage::RemoveObserver(Observer* observer) { // Notify |delegate_| if this is the first subscriber.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (subscribers_.size() == 1)
DCHECK(observer); delegate_->ThumbnailImageBeingObservedChanged(true);
if (observers_.HasObserver(observer)) {
observers_.RemoveObserver(observer);
if (delegate_ && !observers_.might_have_observers())
delegate_->ThumbnailImageBeingObservedChanged(false);
}
}
bool ThumbnailImage::HasObserver(const Observer* observer) const { return subscription;
return observers_.HasObserver(observer);
} }
void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap) { void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap) {
...@@ -147,18 +134,22 @@ void ThumbnailImage::NotifyUncompressedDataObservers(gfx::ImageSkia image) { ...@@ -147,18 +134,22 @@ void ThumbnailImage::NotifyUncompressedDataObservers(gfx::ImageSkia image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (async_operation_finished_callback_) if (async_operation_finished_callback_)
async_operation_finished_callback_.Run(); async_operation_finished_callback_.Run();
for (auto& observer : observers_) {
auto size_hint = observer.GetThumbnailSizeHint(); for (Subscription* subscription : subscribers_) {
observer.OnThumbnailImageAvailable( auto size_hint = subscription->size_hint_;
size_hint ? CropPreviewImage(image, *size_hint) : image); if (subscription->uncompressed_image_callback_)
subscription->uncompressed_image_callback_.Run(
size_hint ? CropPreviewImage(image, *size_hint) : image);
} }
} }
void ThumbnailImage::NotifyCompressedDataObservers( void ThumbnailImage::NotifyCompressedDataObservers(
CompressedThumbnailData data) { CompressedThumbnailData data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_) for (Subscription* subscription : subscribers_) {
observer.OnCompressedThumbnailDataAvailable(data); if (subscription->compressed_image_callback_)
subscription->compressed_image_callback_.Run(data);
}
} }
// static // static
...@@ -216,3 +207,17 @@ gfx::ImageSkia ThumbnailImage::CropPreviewImage( ...@@ -216,3 +207,17 @@ gfx::ImageSkia ThumbnailImage::CropPreviewImage(
source_image.bitmap()->extractSubset(&cropped, gfx::RectToSkIRect(clip_rect)); source_image.bitmap()->extractSubset(&cropped, gfx::RectToSkIRect(clip_rect));
return gfx::ImageSkia::CreateFrom1xBitmap(cropped); return gfx::ImageSkia::CreateFrom1xBitmap(cropped);
} }
void ThumbnailImage::HandleSubscriptionDestroyed(Subscription* subscription) {
// The order of |subscribers_| does not matter. We can simply swap
// |subscription| in |subscribers_| with the last element, then pop it
// off the back.
auto it = std::find(subscribers_.begin(), subscribers_.end(), subscription);
DCHECK(it != subscribers_.end());
std::swap(*it, *(subscribers_.end() - 1));
subscribers_.pop_back();
// If that was the last subscriber, tell |delegate_| (if it still exists).
if (delegate_ && subscribers_.empty())
delegate_->ThumbnailImageBeingObservedChanged(false);
}
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
#ifndef CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ #ifndef CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_
#define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ #define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_
#include <memory>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/callback.h" #include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/observer_list.h" #include "base/observer_list.h"
...@@ -29,16 +31,27 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> { ...@@ -29,16 +31,27 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
using CompressedThumbnailData = using CompressedThumbnailData =
scoped_refptr<base::RefCountedData<std::vector<uint8_t>>>; scoped_refptr<base::RefCountedData<std::vector<uint8_t>>>;
// Observes uncompressed and/or compressed versions of the thumbnail image as class Subscription {
// they are available.
class Observer : public base::CheckedObserver {
public: public:
// Receives uncompressed thumbnail image data. Default is no-op. Subscription() = delete;
virtual void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image); ~Subscription();
// Receives compressed thumbnail image data. Default is no-op. using UncompressedImageCallback =
virtual void OnCompressedThumbnailDataAvailable( base::RepeatingCallback<void(gfx::ImageSkia)>;
CompressedThumbnailData thumbnail_data); using CompressedImageCallback =
base::RepeatingCallback<void(CompressedThumbnailData)>;
// Set callbacks to receive image data. Subscribers are not allowed
// to unsubscribe (by destroying |this|) from the callback. If
// necessary, post a task to destroy it soon after.
void SetUncompressedImageCallback(UncompressedImageCallback callback) {
uncompressed_image_callback_ = std::move(callback);
}
void SetCompressedImageCallback(CompressedImageCallback callback) {
compressed_image_callback_ = std::move(callback);
}
// Provides a desired aspect ratio and minimum size that the observer will // Provides a desired aspect ratio and minimum size that the observer will
// accept. If not specified, or if available thumbnail data is smaller in // accept. If not specified, or if available thumbnail data is smaller in
...@@ -54,7 +67,20 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> { ...@@ -54,7 +67,20 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
// image passed to OnThumbnailImageAvailable fits the needs of the observer // image passed to OnThumbnailImageAvailable fits the needs of the observer
// for display purposes, without the observer having to further crop the // for display purposes, without the observer having to further crop the
// image. The default is unspecified. // image. The default is unspecified.
virtual base::Optional<gfx::Size> GetThumbnailSizeHint() const; void SetSizeHint(const base::Optional<gfx::Size>& size_hint) {
size_hint_ = size_hint;
}
private:
friend class ThumbnailImage;
explicit Subscription(scoped_refptr<ThumbnailImage> thumbnail);
scoped_refptr<ThumbnailImage> thumbnail_;
base::Optional<gfx::Size> size_hint_;
UncompressedImageCallback uncompressed_image_callback_;
CompressedImageCallback compressed_image_callback_;
}; };
// Represents the endpoint // Represents the endpoint
...@@ -77,9 +103,14 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> { ...@@ -77,9 +103,14 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
bool has_data() const { return data_.get(); } bool has_data() const { return data_.get(); }
void AddObserver(Observer* observer); // Subscribe to thumbnail updates. See |Subscription| to set a
void RemoveObserver(Observer* observer); // callback and conigure additional options.
bool HasObserver(const Observer* observer) const; //
// Even if a callback is not set, the subscription influences
// thumbnail capture. It should be destroyed when updates are not
// needed. It is designed to be stored in base::Optional, created and
// destroyed as needed.
std::unique_ptr<Subscription> Subscribe();
// Sets the SkBitmap data and notifies observers with the resulting image. // Sets the SkBitmap data and notifies observers with the resulting image.
void AssignSkBitmap(SkBitmap bitmap); void AssignSkBitmap(SkBitmap bitmap);
...@@ -130,6 +161,8 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> { ...@@ -130,6 +161,8 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
static gfx::ImageSkia CropPreviewImage(const gfx::ImageSkia& source_image, static gfx::ImageSkia CropPreviewImage(const gfx::ImageSkia& source_image,
const gfx::Size& minimum_size); const gfx::Size& minimum_size);
void HandleSubscriptionDestroyed(Subscription* subscription);
Delegate* delegate_; Delegate* delegate_;
// This is a scoped_refptr to immutable data. Once set, the wrapped // This is a scoped_refptr to immutable data. Once set, the wrapped
...@@ -138,7 +171,12 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> { ...@@ -138,7 +171,12 @@ class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
// the old data. // the old data.
CompressedThumbnailData data_; CompressedThumbnailData data_;
base::ObserverList<Observer> observers_; // Subscriptions are inserted on |Subscribe()| calls and removed when
// they are destroyed via callback. The order of subscriber
// notification doesn't matter, so don't maintain any ordering. Since
// the number of subscribers for a given thumbnail is expected to be
// small, doing a linear search to remove a subscriber is fine.
std::vector<Subscription*> subscribers_;
// Called when an asynchronous operation (such as encoding image data upon // Called when an asynchronous operation (such as encoding image data upon
// assignment or decoding image data for observers) finishes or fails. // assignment or decoding image data for observers) finishes or fails.
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
...@@ -20,81 +21,56 @@ namespace { ...@@ -20,81 +21,56 @@ namespace {
constexpr int kTestBitmapWidth = 200; constexpr int kTestBitmapWidth = 200;
constexpr int kTestBitmapHeight = 123; constexpr int kTestBitmapHeight = 123;
// Waits for thumbnail images (or compressed data) and can report how many template <typename... T>
// images it has received. base::RepeatingCallback<void(T...)> IgnoreArgs(
class TestThumbnailImageObserver : public ThumbnailImage::Observer { base::RepeatingCallback<void()> cb) {
public: auto helper = [](T...) {};
// Wait for the uncompressed thumbnail image. return base::BindRepeating(helper).Then(std::move(cb));
void WaitForImage() { }
if (new_image_count_ > last_image_count_) {
last_image_count_ = new_image_count_;
return;
}
// Need a fresh loop since we may have quit out of the last one. class CallbackWaiter {
run_loop_ = std::make_unique<base::RunLoop>(); public:
waiting_for_image_ = true; CallbackWaiter() {
run_loop_->Run(); callback_ = base::BindRepeating(&CallbackWaiter::HandleCallback,
weak_ptr_factory_.GetWeakPtr());
} }
// Wait for compressed thumbnail data. base::RepeatingClosure callback() { return callback_; }
void WaitForCompressedData() {
if (new_compressed_count_ > last_compressed_count_) {
last_compressed_count_ = new_compressed_count_;
return;
}
// Need a fresh loop since we may have quit out of the last one. bool called() const { return called_; }
run_loop_ = std::make_unique<base::RunLoop>();
waiting_for_data_ = true;
run_loop_->Run();
}
int new_image_count() const { return new_image_count_; } void Reset() { called_ = false; }
gfx::ImageSkia thumbnail_image() const { return thumbnail_image_; }
int new_compressed_count() const { return new_compressed_count_; }
ThumbnailImage::CompressedThumbnailData compressed_data() const {
return compressed_data_;
}
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer>* scoped_observer() { void Wait() {
return &scoped_observer_; if (called_)
return;
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
} }
private: private:
// ThumbnailImage::Observer: void HandleCallback() {
void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) override { if (quit_closure_)
++new_image_count_; std::move(quit_closure_).Run();
thumbnail_image_ = thumbnail_image; called_ = true;
if (waiting_for_image_) {
last_image_count_ = new_image_count_;
run_loop_->Quit();
waiting_for_image_ = false;
}
} }
void OnCompressedThumbnailDataAvailable( base::RepeatingClosure callback_;
ThumbnailImage::CompressedThumbnailData thumbnail_data) override { base::OnceClosure quit_closure_;
++new_compressed_count_; bool called_ = false;
compressed_data_ = thumbnail_data;
if (waiting_for_data_) {
last_compressed_count_ = new_compressed_count_;
run_loop_->Quit();
waiting_for_data_ = false;
}
}
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer> scoped_observer_{ base::WeakPtrFactory<CallbackWaiter> weak_ptr_factory_{this};
this}; };
int new_image_count_ = 0;
int last_image_count_ = 0; class StubDelegate : public ThumbnailImage::Delegate {
gfx::ImageSkia thumbnail_image_; public:
int new_compressed_count_ = 0; StubDelegate() = default;
int last_compressed_count_ = 0; ~StubDelegate() override = default;
ThumbnailImage::CompressedThumbnailData compressed_data_;
bool waiting_for_image_ = false; // ThumbnailImage::Delegate:
bool waiting_for_data_ = false; void ThumbnailImageBeingObservedChanged(bool is_being_observed) override {}
std::unique_ptr<base::RunLoop> run_loop_;
}; };
} // anonymous namespace } // anonymous namespace
...@@ -128,169 +104,228 @@ class ThumbnailImageTest : public testing::Test, ...@@ -128,169 +104,228 @@ class ThumbnailImageTest : public testing::Test,
DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest); DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest);
}; };
TEST_F(ThumbnailImageTest, Add_Remove_Observer) { using Subscription = ThumbnailImage::Subscription;
TEST_F(ThumbnailImageTest, AddRemoveSubscriber) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed()); EXPECT_FALSE(is_being_observed());
TestThumbnailImageObserver observer;
image->AddObserver(&observer); std::unique_ptr<Subscription> subscription = image->Subscribe();
EXPECT_TRUE(image->HasObserver(&observer));
EXPECT_TRUE(is_being_observed()); EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer);
EXPECT_FALSE(image->HasObserver(&observer)); subscription.reset();
EXPECT_FALSE(is_being_observed()); EXPECT_FALSE(is_being_observed());
} }
TEST_F(ThumbnailImageTest, Add_Remove_MultipleObservers) { TEST_F(ThumbnailImageTest, AddRemoveMultipleObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed()); EXPECT_FALSE(is_being_observed());
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2; std::unique_ptr<Subscription> subscription1 = image->Subscribe();
image->AddObserver(&observer);
EXPECT_TRUE(image->HasObserver(&observer));
EXPECT_TRUE(is_being_observed()); EXPECT_TRUE(is_being_observed());
image->AddObserver(&observer2);
EXPECT_TRUE(image->HasObserver(&observer2)); std::unique_ptr<Subscription> subscription2 = image->Subscribe();
EXPECT_TRUE(is_being_observed()); EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer);
EXPECT_FALSE(image->HasObserver(&observer)); subscription1.reset();
EXPECT_TRUE(image->HasObserver(&observer2));
EXPECT_TRUE(is_being_observed()); EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer2);
EXPECT_FALSE(image->HasObserver(&observer2)); subscription2.reset();
EXPECT_FALSE(is_being_observed()); EXPECT_FALSE(is_being_observed());
} }
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObservers) { TEST_F(ThumbnailImageTest, AssignSkBitmapNotifiesObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2; std::unique_ptr<Subscription> subscription1 = image->Subscribe();
observer.scoped_observer()->Add(image.get()); std::unique_ptr<Subscription> subscription2 = image->Subscribe();
observer2.scoped_observer()->Add(image.get());
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage();
observer2.WaitForImage(); waiter1.Wait();
EXPECT_EQ(1, observer.new_image_count()); waiter2.Wait();
EXPECT_EQ(1, observer2.new_image_count()); EXPECT_TRUE(waiter1.called());
EXPECT_FALSE(observer.thumbnail_image().isNull()); EXPECT_TRUE(waiter2.called());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer.thumbnail_image().size());
} }
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObserversAgain) { TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObserversAgain) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2; std::unique_ptr<Subscription> subscription1 = image->Subscribe();
observer.scoped_observer()->Add(image.get()); std::unique_ptr<Subscription> subscription2 = image->Subscribe();
observer2.scoped_observer()->Add(image.get());
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage();
observer2.WaitForImage(); waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
waiter1.Reset();
waiter2.Reset();
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage();
observer2.WaitForImage(); waiter1.Wait();
EXPECT_EQ(2, observer.new_image_count()); waiter2.Wait();
EXPECT_EQ(2, observer2.new_image_count()); EXPECT_TRUE(waiter1.called());
EXPECT_FALSE(observer.thumbnail_image().isNull()); EXPECT_TRUE(waiter2.called());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer.thumbnail_image().size());
} }
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObservers) { TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2;
observer.scoped_observer()->Add(image.get());
observer2.scoped_observer()->Add(image.get());
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); std::unique_ptr<Subscription> subscription1 = image->Subscribe();
auto compressed = Compress(bitmap); std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetCompressedImageCallback(
IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetCompressedImageCallback(
IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter2.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForCompressedData();
observer2.WaitForCompressedData(); waiter1.Wait();
EXPECT_EQ(1, observer.new_compressed_count()); waiter2.Wait();
EXPECT_EQ(1, observer2.new_compressed_count()); EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(observer.compressed_data()); EXPECT_TRUE(waiter2.called());
EXPECT_TRUE(observer2.compressed_data());
EXPECT_EQ(compressed, observer.compressed_data()->data);
EXPECT_EQ(compressed, observer2.compressed_data()->data);
} }
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObserversAgain) { TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObserversAgain) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2;
observer.scoped_observer()->Add(image.get());
observer2.scoped_observer()->Add(image.get());
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); std::unique_ptr<Subscription> subscription1 = image->Subscribe();
auto compressed = Compress(bitmap); std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetCompressedImageCallback(
IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetCompressedImageCallback(
IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter2.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForCompressedData();
observer2.WaitForCompressedData(); waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
waiter1.Reset();
waiter2.Reset();
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForCompressedData();
observer2.WaitForCompressedData(); waiter1.Wait();
EXPECT_EQ(2, observer.new_compressed_count()); waiter2.Wait();
EXPECT_EQ(2, observer2.new_compressed_count()); EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(observer.compressed_data()); EXPECT_TRUE(waiter2.called());
EXPECT_TRUE(observer2.compressed_data());
EXPECT_EQ(compressed, observer.compressed_data()->data);
EXPECT_EQ(compressed, observer2.compressed_data()->data);
} }
TEST_F(ThumbnailImageTest, RequestThumbnailImage) { TEST_F(ThumbnailImageTest, RequestThumbnailImage) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
observer.scoped_observer()->Add(image.get()); std::unique_ptr<Subscription> subscription1 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage(); waiter1.Wait();
EXPECT_TRUE(waiter1.called());
waiter1.Reset();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
TestThumbnailImageObserver observer2;
observer2.scoped_observer()->Add(image.get());
image->RequestThumbnailImage(); image->RequestThumbnailImage();
observer.WaitForImage(); waiter1.Wait();
observer2.WaitForImage(); waiter2.Wait();
EXPECT_EQ(2, observer.new_image_count()); EXPECT_TRUE(waiter1.called());
EXPECT_EQ(1, observer2.new_image_count()); EXPECT_TRUE(waiter2.called());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer2.thumbnail_image().size());
} }
TEST_F(ThumbnailImageTest, RequestCompressedThumbnailData) { TEST_F(ThumbnailImageTest, RequestCompressedThumbnailData) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
observer.scoped_observer()->Add(image.get());
SkBitmap bitmap = CreateBitmap(kTestBitmapHeight, kTestBitmapHeight); std::unique_ptr<Subscription> subscription = image->Subscribe();
CallbackWaiter waiter;
subscription->SetCompressedImageCallback(
IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage(); waiter.Wait();
EXPECT_TRUE(waiter.called());
waiter.Reset();
const int count_before_request = observer.new_compressed_count();
image->RequestCompressedThumbnailData(); image->RequestCompressedThumbnailData();
EXPECT_EQ(count_before_request + 1, observer.new_compressed_count()); waiter.Wait();
EXPECT_TRUE(waiter.called());
} }
TEST_F(ThumbnailImageTest, ClearThumbnailWhileNotifyingObservers) { TEST_F(ThumbnailImageTest, ClearThumbnailWhileNotifyingObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this); auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
observer.scoped_observer()->Add(image.get()); std::unique_ptr<Subscription> subscription = image->Subscribe();
CallbackWaiter waiter;
subscription->SetUncompressedImageCallback(
IgnoreArgs<gfx::ImageSkia>(waiter.callback()));
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
observer.WaitForImage(); waiter.Wait();
EXPECT_TRUE(waiter.called());
waiter.Reset();
image->RequestThumbnailImage(); image->RequestThumbnailImage();
image->ClearData(); image->ClearData();
observer.WaitForImage(); waiter.Wait();
EXPECT_TRUE(waiter.called());
}
// Makes sure a null dereference does not happen. Regression test for
// crbug.com/1159701.
TEST_F(ThumbnailImageTest, UnsubscribeAfterDelegateDestroyed) {
auto delegate = std::make_unique<StubDelegate>();
auto image = base::MakeRefCounted<ThumbnailImage>(delegate.get());
std::unique_ptr<Subscription> subscription = image->Subscribe();
// Normally |image| will notify its delegate when the last
// subscription is destroyed. When there is no delegate it shouldn't
// do anything.
delegate.reset();
subscription.reset();
} }
...@@ -29,37 +29,30 @@ ...@@ -29,37 +29,30 @@
namespace { namespace {
class ThumbnailWaiter : public ThumbnailImage::Observer { class ThumbnailWaiter {
public: public:
ThumbnailWaiter() = default; ThumbnailWaiter() = default;
~ThumbnailWaiter() override = default; ~ThumbnailWaiter() = default;
base::Optional<gfx::ImageSkia> WaitForThumbnail(ThumbnailImage* thumbnail) { base::Optional<gfx::ImageSkia> WaitForThumbnail(ThumbnailImage* thumbnail) {
DCHECK(!thumbnail_); std::unique_ptr<ThumbnailImage::Subscription> subscription =
thumbnail_ = thumbnail; thumbnail->Subscribe();
scoped_observer_.Add(thumbnail); subscription->SetUncompressedImageCallback(base::BindRepeating(
thumbnail_->RequestThumbnailImage(); &ThumbnailWaiter::ThumbnailImageCallback, base::Unretained(this)));
thumbnail->RequestThumbnailImage();
run_loop_.Run(); run_loop_.Run();
return image_; return image_;
} }
protected: protected:
// ThumbnailImage::Observer: void ThumbnailImageCallback(gfx::ImageSkia thumbnail_image) {
void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) override { image_ = std::move(thumbnail_image);
if (thumbnail_) { run_loop_.Quit();
scoped_observer_.Remove(thumbnail_);
thumbnail_ = nullptr;
image_ = thumbnail_image;
run_loop_.Quit();
}
} }
private: private:
base::RunLoop run_loop_; base::RunLoop run_loop_;
ThumbnailImage* thumbnail_ = nullptr;
base::Optional<gfx::ImageSkia> image_; base::Optional<gfx::ImageSkia> image_;
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer> scoped_observer_{
this};
}; };
} // anonymous namespace } // anonymous namespace
......
...@@ -396,12 +396,11 @@ class TabHoverCardBubbleView::FadeLabel : public views::Label { ...@@ -396,12 +396,11 @@ class TabHoverCardBubbleView::FadeLabel : public views::Label {
// Maintains a set of thumbnails to watch, ensuring the capture count on the // Maintains a set of thumbnails to watch, ensuring the capture count on the
// associated WebContents stays nonzero until a valid thumbnail has been // associated WebContents stays nonzero until a valid thumbnail has been
// captured. // captured.
class TabHoverCardBubbleView::ThumbnailObserver class TabHoverCardBubbleView::ThumbnailObserver {
: public ThumbnailImage::Observer {
public: public:
explicit ThumbnailObserver(TabHoverCardBubbleView* hover_card) explicit ThumbnailObserver(TabHoverCardBubbleView* hover_card)
: hover_card_(hover_card) {} : hover_card_(hover_card) {}
~ThumbnailObserver() override = default; ~ThumbnailObserver() = default;
// Begin watching the specified thumbnail image for updates. Ideally, should // Begin watching the specified thumbnail image for updates. Ideally, should
// trigger the associated WebContents to load (if not loaded already) and // trigger the associated WebContents to load (if not loaded already) and
...@@ -411,13 +410,17 @@ class TabHoverCardBubbleView::ThumbnailObserver ...@@ -411,13 +410,17 @@ class TabHoverCardBubbleView::ThumbnailObserver
if (current_image_ == thumbnail_image) if (current_image_ == thumbnail_image)
return; return;
scoped_observation_.Reset(); subscription_.reset();
current_image_ = std::move(thumbnail_image); current_image_ = std::move(thumbnail_image);
if (!current_image_)
return;
if (current_image_) { subscription_ = current_image_->Subscribe();
scoped_observation_.Observe(current_image_.get()); subscription_->SetSizeHint(TabStyle::GetPreviewImageSize());
current_image_->RequestThumbnailImage(); subscription_->SetUncompressedImageCallback(base::BindRepeating(
} &ThumbnailObserver::ThumbnailImageCallback, base::Unretained(this)));
current_image_->RequestThumbnailImage();
} }
// Returns the current (most recent) thumbnail being watched. // Returns the current (most recent) thumbnail being watched.
...@@ -425,18 +428,13 @@ class TabHoverCardBubbleView::ThumbnailObserver ...@@ -425,18 +428,13 @@ class TabHoverCardBubbleView::ThumbnailObserver
return current_image_; return current_image_;
} }
base::Optional<gfx::Size> GetThumbnailSizeHint() const override { void ThumbnailImageCallback(gfx::ImageSkia preview_image) {
return TabStyle::GetPreviewImageSize();
}
void OnThumbnailImageAvailable(gfx::ImageSkia preview_image) override {
hover_card_->OnThumbnailImageAvailable(std::move(preview_image)); hover_card_->OnThumbnailImageAvailable(std::move(preview_image));
} }
scoped_refptr<ThumbnailImage> current_image_; scoped_refptr<ThumbnailImage> current_image_;
std::unique_ptr<ThumbnailImage::Subscription> subscription_;
TabHoverCardBubbleView* const hover_card_; TabHoverCardBubbleView* const hover_card_;
base::ScopedObservation<ThumbnailImage, ThumbnailImage::Observer>
scoped_observation_{this};
}; };
TabHoverCardBubbleView::TabHoverCardBubbleView(Tab* tab) TabHoverCardBubbleView::TabHoverCardBubbleView(Tab* tab)
......
...@@ -15,14 +15,17 @@ ...@@ -15,14 +15,17 @@
// Handles requests for a given tab's thumbnail and watches for thumbnail // Handles requests for a given tab's thumbnail and watches for thumbnail
// updates for the lifetime of the tab. // updates for the lifetime of the tab.
class ThumbnailTracker::ContentsData : public content::WebContentsObserver, class ThumbnailTracker::ContentsData : public content::WebContentsObserver {
public ThumbnailImage::Observer {
public: public:
ContentsData(ThumbnailTracker* parent, content::WebContents* contents) ContentsData(ThumbnailTracker* parent, content::WebContents* contents)
: content::WebContentsObserver(contents), parent_(parent) { : content::WebContentsObserver(contents), parent_(parent) {
thumbnail_ = parent_->thumbnail_getter_.Run(contents); thumbnail_ = parent_->thumbnail_getter_.Run(contents);
if (thumbnail_) if (!thumbnail_)
observation_.Observe(thumbnail_.get()); return;
subscription_ = thumbnail_->Subscribe();
subscription_->SetCompressedImageCallback(base::BindRepeating(
&ContentsData::ThumbnailImageCallback, base::Unretained(this)));
} }
void RequestThumbnail() { void RequestThumbnail() {
...@@ -35,8 +38,7 @@ class ThumbnailTracker::ContentsData : public content::WebContentsObserver, ...@@ -35,8 +38,7 @@ class ThumbnailTracker::ContentsData : public content::WebContentsObserver,
// We must un-observe each ThumbnailImage when the WebContents it came from // We must un-observe each ThumbnailImage when the WebContents it came from
// closes. // closes.
if (thumbnail_) { if (thumbnail_) {
DCHECK(observation_.IsObservingSource(thumbnail_.get())); subscription_.reset();
observation_.Reset();
thumbnail_.reset(); thumbnail_.reset();
} }
...@@ -44,17 +46,14 @@ class ThumbnailTracker::ContentsData : public content::WebContentsObserver, ...@@ -44,17 +46,14 @@ class ThumbnailTracker::ContentsData : public content::WebContentsObserver,
parent_->ContentsClosed(web_contents()); parent_->ContentsClosed(web_contents());
} }
// ThumbnailImage::Observer: private:
void OnCompressedThumbnailDataAvailable( void ThumbnailImageCallback(CompressedThumbnailData image) {
CompressedThumbnailData thumbnail_image) override { parent_->ThumbnailUpdated(web_contents(), image);
parent_->ThumbnailUpdated(web_contents(), thumbnail_image);
} }
private:
ThumbnailTracker* parent_; ThumbnailTracker* parent_;
scoped_refptr<ThumbnailImage> thumbnail_; scoped_refptr<ThumbnailImage> thumbnail_;
base::ScopedObservation<ThumbnailImage, ThumbnailImage::Observer> std::unique_ptr<ThumbnailImage::Subscription> subscription_;
observation_{this};
DISALLOW_COPY_AND_ASSIGN(ContentsData); DISALLOW_COPY_AND_ASSIGN(ContentsData);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment