Commit c04ed87e authored by Dana Fried's avatar Dana Fried Committed by Commit Bot

Tab screenshots for tabs that were never visible.

This is a collaboration of fdoray@ (Catan) and dfried@ (Top Chrome) to
improve how preview images are captured for tab hover cards and for
the new touch-enhanced tablet tab-switcher being developed by the
WebUI team in collaboration with the Views and Top Chrome teams.

Tab preview thumbnails are an integral part of our UX plan for tab-
switching improvements in the next few quarters. Previous
infrastructure was aimed at saving images for the NTP, which was a very
different set of capture situations than what we want for tab previews
(specifically, background-loaded tabs were never captured). This logic
allows the capture of tabs loaded in the background or restored from a
previous session by using a different capture mechanism.

This functionality is entirely behind a flag and is known to be slightly
buggy. We plan to use volunteers to teamfood the functionality and send
feedback/issues so it can be made ready for production.

Change-Id: Ie356b95dc42b75729860c772e9a01a19028d0fee
Bug: 981103
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1689397
Commit-Queue: Dana Fried <dfried@chromium.org>
Reviewed-by: default avatarDana Fried <dfried@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683732}
parent 3e1b2288
...@@ -530,6 +530,7 @@ jumbo_split_static_library("ui") { ...@@ -530,6 +530,7 @@ jumbo_split_static_library("ui") {
"//components/variations/service", "//components/variations/service",
"//components/vector_icons", "//components/vector_icons",
"//components/version_ui", "//components/version_ui",
"//components/viz/host",
"//components/web_cache/browser", "//components/web_cache/browser",
"//components/web_resource", "//components/web_resource",
"//components/webrtc_logging/browser", "//components/webrtc_logging/browser",
......
...@@ -8,104 +8,107 @@ ...@@ -8,104 +8,107 @@
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/jpeg_codec.h"
// Refcounted class that stores compressed JPEG data. namespace {
class ThumbnailImage::ThumbnailData
: public base::RefCountedThreadSafe<ThumbnailData> {
public:
static gfx::ImageSkia ToImageSkia(
scoped_refptr<ThumbnailData> representation) {
const auto& data = representation->data_;
gfx::ImageSkia result = gfx::ImageSkia::CreateFrom1xBitmap(
*gfx::JPEGCodec::Decode(data.data(), data.size()));
result.MakeThreadSafe();
return result;
}
static scoped_refptr<ThumbnailData> FromSkBitmap(SkBitmap bitmap) {
constexpr int kCompressionQuality = 97;
std::vector<uint8_t> data;
const bool result =
gfx::JPEGCodec::Encode(bitmap, kCompressionQuality, &data);
DCHECK(result);
return scoped_refptr<ThumbnailData>(new ThumbnailData(std::move(data)));
}
size_t size() const { return data_.size(); } std::vector<uint8_t> SkBitmapToJPEGData(SkBitmap bitmap) {
constexpr int kCompressionQuality = 97;
std::vector<uint8_t> data;
const bool result =
gfx::JPEGCodec::Encode(bitmap, kCompressionQuality, &data);
DCHECK(result);
return data;
}
private: gfx::ImageSkia JPEGDataToImageSkia(
friend base::RefCountedThreadSafe<ThumbnailData>; scoped_refptr<base::RefCountedData<std::vector<uint8_t>>> data) {
gfx::ImageSkia result = gfx::ImageSkia::CreateFrom1xBitmap(
*gfx::JPEGCodec::Decode(data->data.data(), data->data.size()));
result.MakeThreadSafe();
return result;
}
explicit ThumbnailData(std::vector<uint8_t>&& data) } // namespace
: data_(std::move(data)) {}
~ThumbnailData() = default; ThumbnailImage::Delegate::~Delegate() {
if (thumbnail_)
thumbnail_->delegate_ = nullptr;
}
std::vector<uint8_t> data_; ThumbnailImage::ThumbnailImage(Delegate* delegate) : delegate_(delegate) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(delegate_);
DCHECK(!delegate_->thumbnail_);
delegate_->thumbnail_ = this;
}
DISALLOW_COPY_AND_ASSIGN(ThumbnailData); ThumbnailImage::~ThumbnailImage() {
}; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (delegate_)
delegate_->thumbnail_ = nullptr;
}
ThumbnailImage::ThumbnailImage() = default; void ThumbnailImage::AddObserver(Observer* observer) {
ThumbnailImage::~ThumbnailImage() = default; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ThumbnailImage::ThumbnailImage(const ThumbnailImage& other) = default; DCHECK(observer);
ThumbnailImage::ThumbnailImage(ThumbnailImage&& other) = default; if (!observers_.HasObserver(observer)) {
ThumbnailImage& ThumbnailImage::operator=(const ThumbnailImage& other) = const bool is_first_observer = !observers_.might_have_observers();
default; observers_.AddObserver(observer);
ThumbnailImage& ThumbnailImage::operator=(ThumbnailImage&& other) = default; if (is_first_observer && delegate_)
delegate_->ThumbnailImageBeingObservedChanged(true);
}
}
gfx::ImageSkia ThumbnailImage::AsImageSkia() const { void ThumbnailImage::RemoveObserver(Observer* observer) {
return ThumbnailData::ToImageSkia(image_representation_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
if (observers_.HasObserver(observer)) {
observers_.RemoveObserver(observer);
if (delegate_ && !observers_.might_have_observers())
delegate_->ThumbnailImageBeingObservedChanged(false);
}
} }
bool ThumbnailImage::AsImageSkiaAsync(AsImageSkiaCallback callback) const { bool ThumbnailImage::HasObserver(const Observer* observer) const {
if (!HasData()) return observers_.HasObserver(observer);
return false; }
void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap) {
base::PostTaskWithTraitsAndReplyWithResult( base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, FROM_HERE,
{base::TaskPriority::USER_VISIBLE, {base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::ToImageSkia, image_representation_), base::BindOnce(&SkBitmapToJPEGData, std::move(bitmap)),
std::move(callback)); base::BindOnce(&ThumbnailImage::AssignJPEGData,
return true; weak_ptr_factory_.GetWeakPtr()));
}
bool ThumbnailImage::HasData() const {
return static_cast<bool>(image_representation_);
} }
size_t ThumbnailImage::GetStorageSize() const { void ThumbnailImage::RequestThumbnailImage() {
return image_representation_ ? image_representation_->size() : 0; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ConvertJPEGDataToImageSkiaAndNotifyObservers();
} }
bool ThumbnailImage::BackedBySameObjectAs(const ThumbnailImage& other) const { void ThumbnailImage::AssignJPEGData(std::vector<uint8_t> data) {
return image_representation_.get() == other.image_representation_.get(); data_ = base::MakeRefCounted<base::RefCountedData<std::vector<uint8_t>>>(
std::move(data));
ConvertJPEGDataToImageSkiaAndNotifyObservers();
} }
// static bool ThumbnailImage::ConvertJPEGDataToImageSkiaAndNotifyObservers() {
ThumbnailImage ThumbnailImage::FromSkBitmap(SkBitmap bitmap) { if (!data_)
ThumbnailImage result; return false;
result.image_representation_ = ThumbnailData::FromSkBitmap(bitmap); return base::PostTaskWithTraitsAndReplyWithResult(
return result;
}
// static
void ThumbnailImage::FromSkBitmapAsync(SkBitmap bitmap,
CreateThumbnailCallback callback) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, FROM_HERE,
{base::TaskPriority::USER_VISIBLE, {base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::FromSkBitmap, bitmap), base::BindOnce(&JPEGDataToImageSkia, data_),
base::BindOnce( base::BindOnce(&ThumbnailImage::NotifyObservers,
[](CreateThumbnailCallback callback, weak_ptr_factory_.GetWeakPtr()));
scoped_refptr<ThumbnailData> representation) { }
ThumbnailImage result;
result.image_representation_ = representation; void ThumbnailImage::NotifyObservers(gfx::ImageSkia image) {
std::move(callback).Run(result); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}, for (auto& observer : observers_)
std::move(callback))); observer.OnThumbnailImageAvailable(image);
} }
...@@ -9,64 +9,73 @@ ...@@ -9,64 +9,73 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia.h"
// Value type holding thumbnail image data. Data is internally refcounted, so // Stores compressed thumbnail data for a tab and can vend that data as an
// copying this object is inexpensive. // uncompressed image to observers.
// class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
// Internally, data is stored as a ThumbnailRepresentation, which is optionally
// compressed and not directly renderable, so that holding a thumbnail from each
// tab in memory isn't prohibitively expensive. Because of this, and because
// converting from the uncompressed to the compressed format may be expensive,
// asynchronous methods for converting to and from ThumbnailImage are provided.
class ThumbnailImage {
public: public:
// Callback used by AsImageSkiaAsync. The parameter is the retrieved image. // Observes uncompressed versions of the thumbnail image as they are
using AsImageSkiaCallback = base::OnceCallback<void(gfx::ImageSkia)>; // available.
class Observer : public base::CheckedObserver {
public:
virtual void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) = 0;
};
// Represents the endpoint
class Delegate {
public:
// Called whenever the thumbnail starts or stops being observed.
// Because updating the thumbnail could be an expensive operation, it's
// useful to track when there are no observers. Default behavior is no-op.
virtual void ThumbnailImageBeingObservedChanged(bool is_being_observed) = 0;
protected:
virtual ~Delegate();
private:
friend class ThumbnailImage;
ThumbnailImage* thumbnail_ = nullptr;
};
explicit ThumbnailImage(Delegate* delegate);
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
bool HasObserver(const Observer* observer) const;
// Sets the SkBitmap data and notifies observers with the resulting image.
void AssignSkBitmap(SkBitmap bitmap);
// Requests that a thumbnail image be made available to observers. Does not
// guarantee that Observer::OnThumbnailImageAvailable() will be called, or how
// long it will take, though in most cases it should happen very quickly.
virtual void RequestThumbnailImage();
// Callback used by FromSkBitmapAsync. The parameter is the created thumbnail. private:
using CreateThumbnailCallback = base::OnceCallback<void(ThumbnailImage)>; friend class Delegate;
friend class base::RefCounted<ThumbnailImage>;
ThumbnailImage();
~ThumbnailImage();
ThumbnailImage(const ThumbnailImage& other);
ThumbnailImage(ThumbnailImage&& other);
ThumbnailImage& operator=(const ThumbnailImage& other);
ThumbnailImage& operator=(ThumbnailImage&& other);
// Retrieves the thumbnail data as a renderable image. May involve image
// decoding, so could be expensive. Prefer AsImageSkiaAsync() instead.
gfx::ImageSkia AsImageSkia() const;
// Retrieves the thumbnail data as a renderable image. May involve image virtual ~ThumbnailImage();
// decoding on a background thread. Callback is called when the operation
// completes. Returns false if there is no thumbnail data.
bool AsImageSkiaAsync(AsImageSkiaCallback callback) const;
// Returns whether there is data stored in the thumbnail. void AssignJPEGData(std::vector<uint8_t> data);
bool HasData() const; bool ConvertJPEGDataToImageSkiaAndNotifyObservers();
void NotifyObservers(gfx::ImageSkia image);
// Returns the size (in bytes) required to store the internal representation. Delegate* delegate_;
size_t GetStorageSize() const;
// Does an address comparison on the refcounted backing store of this object. scoped_refptr<base::RefCountedData<std::vector<uint8_t>>> data_;
// May return false even if the backing stores contain equivalent image data.
bool BackedBySameObjectAs(const ThumbnailImage& other) const;
// Encodes thumbnail data as ThumbnailRepresentation. May involve an expensive base::ObserverList<Observer> observers_;
// operation. Prefer FromSkBitmapAsync() instead.
static ThumbnailImage FromSkBitmap(SkBitmap bitmap);
// Converts the bitmap data to the internal ThumbnailRepresentation. May SEQUENCE_CHECKER(sequence_checker_);
// involve image encoding or compression on a background thread. Callback is
// called when the operation completes.
static void FromSkBitmapAsync(SkBitmap bitmap,
CreateThumbnailCallback callback);
private: base::WeakPtrFactory<ThumbnailImage> weak_ptr_factory_{this};
class ThumbnailData;
scoped_refptr<ThumbnailData> image_representation_; DISALLOW_COPY_AND_ASSIGN(ThumbnailImage);
}; };
#endif // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_ #endif // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_
...@@ -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/run_loop.h" #include "base/run_loop.h"
#include "base/scoped_observer.h"
#include "base/test/scoped_task_environment.h" #include "base/test/scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia.h"
...@@ -16,9 +17,54 @@ ...@@ -16,9 +17,54 @@
namespace { namespace {
constexpr int kTestBitmapWidth = 200; constexpr int kTestBitmapWidth = 200;
constexpr int kTestBitmapHeight = 123; constexpr int kTestBitmapHeight = 123;
// Waits for thumbnail images and can report how many images it has received.
class TestThumbnailImageObserver : public ThumbnailImage::Observer {
public:
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.
run_loop_ = std::make_unique<base::RunLoop>();
waiting_ = true;
run_loop_->Run();
}
int new_image_count() const { return new_image_count_; }
gfx::ImageSkia thumbnail_image() const { return thumbnail_image_; }
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer>* scoped_observer() {
return &scoped_observer_;
}
private:
// ThumbnailImage::Observer:
void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) override {
++new_image_count_;
thumbnail_image_ = thumbnail_image;
if (waiting_) {
last_image_count_ = new_image_count_;
run_loop_->Quit();
waiting_ = false;
}
}
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer> scoped_observer_{
this};
int new_image_count_ = 0;
int last_image_count_ = 0;
gfx::ImageSkia thumbnail_image_;
bool waiting_ = false;
std::unique_ptr<base::RunLoop> run_loop_;
};
} // namespace } // namespace
class ThumbnailImageTest : public testing::Test { class ThumbnailImageTest : public testing::Test,
public ThumbnailImage::Delegate {
public: public:
ThumbnailImageTest() = default; ThumbnailImageTest() = default;
...@@ -30,72 +76,108 @@ class ThumbnailImageTest : public testing::Test { ...@@ -30,72 +76,108 @@ class ThumbnailImageTest : public testing::Test {
return bitmap; return bitmap;
} }
bool is_being_observed() const { return is_being_observed_; }
private: private:
void ThumbnailImageBeingObservedChanged(bool is_being_observed) override {
is_being_observed_ = is_being_observed;
}
bool is_being_observed_ = false;
base::test::ScopedTaskEnvironment scoped_task_environment_; base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest); DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest);
}; };
TEST_F(ThumbnailImageTest, FromSkBitmap) { TEST_F(ThumbnailImageTest, Add_Remove_Observer) {
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); auto image = base::MakeRefCounted<ThumbnailImage>(this);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap); EXPECT_FALSE(is_being_observed());
EXPECT_TRUE(image.HasData()); TestThumbnailImageObserver observer;
EXPECT_GE(image.GetStorageSize(), 0U); image->AddObserver(&observer);
EXPECT_LE(image.GetStorageSize(), bitmap.computeByteSize()); EXPECT_TRUE(image->HasObserver(&observer));
gfx::ImageSkia image_skia = image.AsImageSkia(); EXPECT_TRUE(is_being_observed());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size()); image->RemoveObserver(&observer);
EXPECT_FALSE(image->HasObserver(&observer));
EXPECT_FALSE(is_being_observed());
} }
TEST_F(ThumbnailImageTest, FromSkBitmapAsync) { TEST_F(ThumbnailImageTest, Add_Remove_MultipleObservers) {
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); auto image = base::MakeRefCounted<ThumbnailImage>(this);
ThumbnailImage image; EXPECT_FALSE(is_being_observed());
TestThumbnailImageObserver observer;
base::RunLoop run_loop; TestThumbnailImageObserver observer2;
ThumbnailImage::FromSkBitmapAsync( image->AddObserver(&observer);
bitmap, base::BindOnce( EXPECT_TRUE(image->HasObserver(&observer));
[](base::OnceClosure end_closure, ThumbnailImage* dest_image, EXPECT_TRUE(is_being_observed());
ThumbnailImage source_image) { image->AddObserver(&observer2);
*dest_image = source_image; EXPECT_TRUE(image->HasObserver(&observer2));
std::move(end_closure).Run(); EXPECT_TRUE(is_being_observed());
}, image->RemoveObserver(&observer);
run_loop.QuitClosure(), base::Unretained(&image))); EXPECT_FALSE(image->HasObserver(&observer));
run_loop.Run(); EXPECT_TRUE(image->HasObserver(&observer2));
EXPECT_TRUE(is_being_observed());
EXPECT_TRUE(image.HasData()); image->RemoveObserver(&observer2);
EXPECT_GE(image.GetStorageSize(), 0U); EXPECT_FALSE(image->HasObserver(&observer2));
EXPECT_LE(image.GetStorageSize(), bitmap.computeByteSize()); EXPECT_FALSE(is_being_observed());
gfx::ImageSkia image_skia = image.AsImageSkia();
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
} }
TEST_F(ThumbnailImageTest, AsImageSkia) { TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObservers) {
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); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
EXPECT_TRUE(image.HasData()); observer.WaitForImage();
observer2.WaitForImage();
EXPECT_EQ(1, observer.new_image_count());
EXPECT_EQ(1, observer2.new_image_count());
EXPECT_FALSE(observer.thumbnail_image().isNull());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer.thumbnail_image().size());
}
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObserversAgain) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2;
observer.scoped_observer()->Add(image.get());
observer2.scoped_observer()->Add(image.get());
gfx::ImageSkia image_skia = image.AsImageSkia(); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
EXPECT_TRUE(image_skia.IsThreadSafe()); image->AssignSkBitmap(bitmap);
EXPECT_FALSE(image_skia.isNull()); observer.WaitForImage();
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size()); observer2.WaitForImage();
image->AssignSkBitmap(bitmap);
observer.WaitForImage();
observer2.WaitForImage();
EXPECT_EQ(2, observer.new_image_count());
EXPECT_EQ(2, observer2.new_image_count());
EXPECT_FALSE(observer.thumbnail_image().isNull());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer.thumbnail_image().size());
} }
TEST_F(ThumbnailImageTest, AsImageSkiaAsync) { TEST_F(ThumbnailImageTest, RequestThumbnailImage) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
TestThumbnailImageObserver observer;
observer.scoped_observer()->Add(image.get());
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight); SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap); image->AssignSkBitmap(bitmap);
EXPECT_TRUE(image.HasData()); observer.WaitForImage();
base::RunLoop run_loop; TestThumbnailImageObserver observer2;
gfx::ImageSkia image_skia; observer2.scoped_observer()->Add(image.get());
const bool can_convert = image.AsImageSkiaAsync(base::BindOnce( image->RequestThumbnailImage();
[](base::OnceClosure end_closure, gfx::ImageSkia* dest_image, observer.WaitForImage();
gfx::ImageSkia source_image) { observer2.WaitForImage();
*dest_image = source_image; EXPECT_EQ(2, observer.new_image_count());
std::move(end_closure).Run(); EXPECT_EQ(1, observer2.new_image_count());
}, EXPECT_FALSE(observer2.thumbnail_image().isNull());
run_loop.QuitClosure(), base::Unretained(&image_skia))); EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
ASSERT_TRUE(can_convert); observer2.thumbnail_image().size());
run_loop.Run();
EXPECT_TRUE(image_skia.IsThreadSafe());
EXPECT_FALSE(image_skia.isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
} }
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_TAB_HELPER_H_ #ifndef CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_TAB_HELPER_H_
#define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_TAB_HELPER_H_ #define CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_TAB_HELPER_H_
#include <memory>
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
...@@ -12,70 +14,72 @@ ...@@ -12,70 +14,72 @@
#include "chrome/browser/ui/thumbnails/thumbnail_image.h" #include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include "chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h" #include "chrome/browser/ui/thumbnails/thumbnail_page_event_adapter.h"
#include "chrome/browser/ui/thumbnails/thumbnail_page_observer.h" #include "chrome/browser/ui/thumbnails/thumbnail_page_observer.h"
#include "components/viz/host/client_frame_sink_video_capturer.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_contents_user_data.h"
class ThumbnailTabHelper class ThumbnailTabHelper
: public thumbnails::ThumbnailPageObserver, : public content::WebContentsUserData<ThumbnailTabHelper>,
public content::WebContentsUserData<ThumbnailTabHelper> { public content::WebContentsObserver,
public viz::mojom::FrameSinkVideoConsumer,
public ThumbnailImage::Delegate {
public: public:
~ThumbnailTabHelper() override; ~ThumbnailTabHelper() override;
ThumbnailImage thumbnail() const { return thumbnail_; } scoped_refptr<ThumbnailImage> thumbnail() const { return thumbnail_; }
protected:
// ThumbnailWebContentsObserver:
void TopLevelNavigationStarted(const GURL& url) override;
void TopLevelNavigationEnded(const GURL& url) override;
void PagePainted() override;
void PageLoadStarted() override;
void PageLoadFinished() override;
void VisibilityChanged(bool visible) override;
content::WebContents* web_contents() const { return adapter_.web_contents(); }
private: private:
// Loading is treated as a state machine for each new URL, and the state for class ThumanailImageImpl;
// each new URL the current tab loads can only advance as events are received friend class content::WebContentsUserData<ThumbnailTabHelper>;
// (so it is not possible to go from kLoadStarted to kNavigationStarted). friend class ThumanailImageImpl;
enum class LoadingState : int32_t {
kNone = 0,
kNavigationStarted = 1,
kNavigationFinished = 2,
kLoadStarted = 3,
kLoadFinished = 4
};
explicit ThumbnailTabHelper(content::WebContents* contents); explicit ThumbnailTabHelper(content::WebContents* contents);
friend class content::WebContentsUserData<ThumbnailTabHelper>;
void StartThumbnailCapture(); // ThumbnailImage::Delegate:
void ProcessCapturedThumbnail(base::TimeTicks start_time, void ThumbnailImageBeingObservedChanged(bool is_being_observed) override;
const SkBitmap& bitmap);
void StoreThumbnail(base::TimeTicks start_time, ThumbnailImage thumbnail); bool ShouldKeepUpdatingThumbnail() const;
void NotifyTabPreviewChanged();
void StartVideoCapture();
void StopVideoCapture();
void CaptureThumbnailOnTabSwitch();
void StoreThumbnail(const SkBitmap& bitmap);
content::RenderWidgetHostView* GetView();
void TransitionLoadingState(LoadingState state, const GURL& url); // content::WebContentsObserver:
void ClearThumbnail(); void OnVisibilityChanged(content::Visibility visibility) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
ThumbnailImage thumbnail_; // viz::mojom::FrameSinkVideoConsumer:
void OnFrameCaptured(
base::ReadOnlySharedMemoryRegion data,
::media::mojom::VideoFrameInfoPtr info,
const gfx::Rect& content_rect,
::viz::mojom::FrameSinkVideoConsumerFrameCallbacksPtr callbacks) override;
void OnStopped() override;
// Caches whether or not the web contents view is visible. See notes in // The last known visibility WebContents visibility.
// VisibilityChanged() for more information. content::Visibility last_visibility_;
bool view_is_visible_; // set in constructor
bool page_painted_ = false; // Whether a thumbnail was captured while the tab was loaded, since the tab
// was last hidden.
bool captured_loaded_thumbnail_since_tab_hidden_ = false;
LoadingState loading_state_ = LoadingState::kNone; // Captures frames from the WebContents while it's hidden. The capturer count
GURL current_url_; // of the WebContents is incremented/decremented when a capturer is set/unset.
std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer_;
thumbnails::ThumbnailPageEventAdapter adapter_; // The thumbnail maintained by this instance.
ScopedObserver<thumbnails::ThumbnailPageEventAdapter, scoped_refptr<ThumbnailImage> thumbnail_ =
thumbnails::ThumbnailPageObserver> base::MakeRefCounted<ThumbnailImage>(this);
scoped_observer_;
WEB_CONTENTS_USER_DATA_KEY_DECL(); WEB_CONTENTS_USER_DATA_KEY_DECL();
base::WeakPtrFactory<ThumbnailTabHelper> weak_factory_{this}; base::WeakPtrFactory<ThumbnailTabHelper>
weak_factory_for_thumbnail_on_tab_hidden_{this};
DISALLOW_COPY_AND_ASSIGN(ThumbnailTabHelper); DISALLOW_COPY_AND_ASSIGN(ThumbnailTabHelper);
}; };
......
...@@ -425,6 +425,7 @@ void TabHoverCardBubbleView::UpdateAndShow(Tab* tab) { ...@@ -425,6 +425,7 @@ void TabHoverCardBubbleView::UpdateAndShow(Tab* tab) {
void TabHoverCardBubbleView::FadeOutToHide() { void TabHoverCardBubbleView::FadeOutToHide() {
delayed_show_timer_.Stop(); delayed_show_timer_.Stop();
RegisterToThumbnailImageUpdates(nullptr);
if (!widget_->IsVisible()) if (!widget_->IsVisible())
return; return;
slide_animation_delegate_->StopAnimation(); slide_animation_delegate_->StopAnimation();
...@@ -524,40 +525,56 @@ void TabHoverCardBubbleView::UpdateCardContent(const Tab* tab) { ...@@ -524,40 +525,56 @@ void TabHoverCardBubbleView::UpdateCardContent(const Tab* tab) {
// If the preview image feature is not enabled, |preview_image_| will be null. // If the preview image feature is not enabled, |preview_image_| will be null.
if (preview_image_ && preview_image_->GetVisible()) { if (preview_image_ && preview_image_->GetVisible()) {
// If there is no valid thumbnail data, blank out the preview, else wait for if (tab->data().thumbnail != thumbnail_image_)
// the image data to be decoded and update momentarily. ClearPreviewImage();
if (!tab->data().thumbnail.AsImageSkiaAsync( RegisterToThumbnailImageUpdates(tab->data().thumbnail);
base::BindOnce(&TabHoverCardBubbleView::UpdatePreviewImage, }
weak_factory_.GetWeakPtr()))) { }
// Check the no-preview color and size to see if it needs to be
// regenerated. DPI or theme change can cause a regeneration. void TabHoverCardBubbleView::RegisterToThumbnailImageUpdates(
const SkColor foreground_color = tab->GetThemeProvider()->GetColor( scoped_refptr<ThumbnailImage> thumbnail_image) {
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_FOREGROUND); if (thumbnail_image_ == thumbnail_image)
return;
// Set the no-preview placeholder image. All sizes are in DIPs. if (thumbnail_image_) {
// gfx::CreateVectorIcon() caches its result so there's no need to store thumbnail_observer_.Remove(thumbnail_image_.get());
// images here; if a particular size/color combination has already been thumbnail_image_.reset();
// requested it will be low-cost to request it again. }
constexpr gfx::Size kNoPreviewImageSize{64, 64}; if (thumbnail_image) {
const gfx::ImageSkia no_preview_image = gfx::CreateVectorIcon( thumbnail_image_ = thumbnail_image;
kGlobeIcon, kNoPreviewImageSize.width(), foreground_color); thumbnail_observer_.Add(thumbnail_image_.get());
preview_image_->SetImage(no_preview_image); thumbnail_image->RequestThumbnailImage();
preview_image_->SetImageSize(kNoPreviewImageSize); }
preview_image_->SetPreferredSize(GetTabHoverCardPreviewImageSize()); }
// Also possibly regenerate the background if it has changed. void TabHoverCardBubbleView::ClearPreviewImage() {
const SkColor background_color = tab->GetThemeProvider()->GetColor( // Check the no-preview color and size to see if it needs to be
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_BACKGROUND); // regenerated. DPI or theme change can cause a regeneration.
if (!preview_image_->background() || const SkColor foreground_color = GetThemeProvider()->GetColor(
preview_image_->background()->get_color() != background_color) { ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_FOREGROUND);
preview_image_->SetBackground(
views::CreateSolidBackground(background_color)); // Set the no-preview placeholder image. All sizes are in DIPs.
} // gfx::CreateVectorIcon() caches its result so there's no need to store
} // images here; if a particular size/color combination has already been
// requested it will be low-cost to request it again.
constexpr gfx::Size kNoPreviewImageSize{64, 64};
const gfx::ImageSkia no_preview_image = gfx::CreateVectorIcon(
kGlobeIcon, kNoPreviewImageSize.width(), foreground_color);
preview_image_->SetImage(no_preview_image);
preview_image_->SetImageSize(kNoPreviewImageSize);
preview_image_->SetPreferredSize(GetTabHoverCardPreviewImageSize());
// Also possibly regenerate the background if it has changed.
const SkColor background_color = GetThemeProvider()->GetColor(
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_BACKGROUND);
if (!preview_image_->background() ||
preview_image_->background()->get_color() != background_color) {
preview_image_->SetBackground(
views::CreateSolidBackground(background_color));
} }
} }
void TabHoverCardBubbleView::UpdatePreviewImage(gfx::ImageSkia preview_image) { void TabHoverCardBubbleView::OnThumbnailImageAvailable(
gfx::ImageSkia preview_image) {
preview_image_->SetImage(preview_image); preview_image_->SetImage(preview_image);
preview_image_->SetImageSize(GetTabHoverCardPreviewImageSize()); preview_image_->SetImageSize(GetTabHoverCardPreviewImageSize());
preview_image_->SetPreferredSize(GetTabHoverCardPreviewImageSize()); preview_image_->SetPreferredSize(GetTabHoverCardPreviewImageSize());
......
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
#include <memory> #include <memory>
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h"
namespace gfx { namespace gfx {
...@@ -25,7 +27,8 @@ class Widget; ...@@ -25,7 +27,8 @@ class Widget;
class Tab; class Tab;
// Dialog that displays an informational hover card containing page information. // Dialog that displays an informational hover card containing page information.
class TabHoverCardBubbleView : public views::BubbleDialogDelegateView { class TabHoverCardBubbleView : public views::BubbleDialogDelegateView,
public ThumbnailImage::Observer {
public: public:
explicit TabHoverCardBubbleView(Tab* tab); explicit TabHoverCardBubbleView(Tab* tab);
...@@ -69,8 +72,15 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView { ...@@ -69,8 +72,15 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView {
// Updates and formats title, domain, and preview image. // Updates and formats title, domain, and preview image.
void UpdateCardContent(const Tab* tab); void UpdateCardContent(const Tab* tab);
void UpdatePreviewImage(gfx::ImageSkia preview_image); void RegisterToThumbnailImageUpdates(
scoped_refptr<ThumbnailImage> thumbnail_image);
void ClearPreviewImage();
// ThumbnailImage::Observer:
void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) override;
// views::BubbleDialogDelegateView:
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
void RecordTimeSinceLastSeenMetric(base::TimeDelta elapsed_time); void RecordTimeSinceLastSeenMetric(base::TimeDelta elapsed_time);
...@@ -100,6 +110,9 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView { ...@@ -100,6 +110,9 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView {
// Counter used to keey track of the number of tab hover cards seen before a // Counter used to keey track of the number of tab hover cards seen before a
// tab is selected by mouse press. // tab is selected by mouse press.
size_t hover_cards_seen_count_ = 0; size_t hover_cards_seen_count_ = 0;
scoped_refptr<ThumbnailImage> thumbnail_image_;
ScopedObserver<ThumbnailImage, ThumbnailImage::Observer> thumbnail_observer_{
this};
base::WeakPtrFactory<TabHoverCardBubbleView> weak_factory_{this}; base::WeakPtrFactory<TabHoverCardBubbleView> weak_factory_{this};
......
...@@ -19,9 +19,8 @@ TabRendererData::~TabRendererData() = default; ...@@ -19,9 +19,8 @@ TabRendererData::~TabRendererData() = default;
bool TabRendererData::operator==(const TabRendererData& other) const { bool TabRendererData::operator==(const TabRendererData& other) const {
return favicon.BackedBySameObjectAs(other.favicon) && return favicon.BackedBySameObjectAs(other.favicon) &&
thumbnail.BackedBySameObjectAs(other.thumbnail) && thumbnail == other.thumbnail && network_state == other.network_state &&
network_state == other.network_state && title == other.title && title == other.title && visible_url == other.visible_url &&
visible_url == other.visible_url &&
last_committed_url == other.last_committed_url && last_committed_url == other.last_committed_url &&
crashed_status == other.crashed_status && crashed_status == other.crashed_status &&
incognito == other.incognito && show_icon == other.show_icon && incognito == other.incognito && show_icon == other.show_icon &&
......
...@@ -30,7 +30,7 @@ struct TabRendererData { ...@@ -30,7 +30,7 @@ struct TabRendererData {
bool IsCrashed() const; bool IsCrashed() const;
gfx::ImageSkia favicon; gfx::ImageSkia favicon;
ThumbnailImage thumbnail; scoped_refptr<ThumbnailImage> thumbnail;
TabNetworkState network_state = TabNetworkState::kNone; TabNetworkState network_state = TabNetworkState::kNone;
base::string16 title; base::string16 title;
// This corresponds to WebContents::GetVisibleUrl(). // This corresponds to WebContents::GetVisibleUrl().
......
...@@ -1452,14 +1452,26 @@ void WebContentsImpl::IncrementCapturerCount(const gfx::Size& capture_size) { ...@@ -1452,14 +1452,26 @@ void WebContentsImpl::IncrementCapturerCount(const gfx::Size& capture_size) {
} }
if (GetVisibility() != Visibility::VISIBLE && !was_captured) { if (GetVisibility() != Visibility::VISIBLE && !was_captured) {
// Ensure that all views act as if they were visible before capture begins. // TODO: Share code with WasShown().
// TODO(fdoray): Replace RenderWidgetHostView::WasUnOccluded() with a method SendPageMessage(new PageMsg_WasShown(MSG_ROUTING_NONE));
// to explicitly notify the RenderWidgetHostView that capture began.
// https://crbug.com/668690 if (auto* view = GetRenderWidgetHostView()) {
if (auto* main_view = GetRenderWidgetHostView()) view->Show();
main_view->WasUnOccluded(); #if defined(OS_MACOSX)
view->SetActive(true);
#endif
}
if (!ShowingInterstitialPage()) if (!ShowingInterstitialPage())
SetVisibilityForChildViews(true); SetVisibilityForChildViews(true);
for (FrameTreeNode* node : frame_tree_.Nodes()) {
RenderFrameProxyHost* parent = node->render_manager()->GetProxyToParent();
if (!parent)
continue;
parent->cross_process_frame_connector()->DelegateWasShown();
}
} }
} }
......
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