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") {
"//components/variations/service",
"//components/vector_icons",
"//components/version_ui",
"//components/viz/host",
"//components/web_cache/browser",
"//components/web_resource",
"//components/webrtc_logging/browser",
......
......@@ -8,104 +8,107 @@
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/codec/jpeg_codec.h"
// Refcounted class that stores compressed JPEG data.
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)));
}
namespace {
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:
friend base::RefCountedThreadSafe<ThumbnailData>;
gfx::ImageSkia JPEGDataToImageSkia(
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)
: data_(std::move(data)) {}
} // namespace
~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;
ThumbnailImage::~ThumbnailImage() = default;
ThumbnailImage::ThumbnailImage(const ThumbnailImage& other) = default;
ThumbnailImage::ThumbnailImage(ThumbnailImage&& other) = default;
ThumbnailImage& ThumbnailImage::operator=(const ThumbnailImage& other) =
default;
ThumbnailImage& ThumbnailImage::operator=(ThumbnailImage&& other) = default;
void ThumbnailImage::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
if (!observers_.HasObserver(observer)) {
const bool is_first_observer = !observers_.might_have_observers();
observers_.AddObserver(observer);
if (is_first_observer && delegate_)
delegate_->ThumbnailImageBeingObservedChanged(true);
}
}
gfx::ImageSkia ThumbnailImage::AsImageSkia() const {
return ThumbnailData::ToImageSkia(image_representation_);
void ThumbnailImage::RemoveObserver(Observer* observer) {
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 {
if (!HasData())
return false;
bool ThumbnailImage::HasObserver(const Observer* observer) const {
return observers_.HasObserver(observer);
}
void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::ToImageSkia, image_representation_),
std::move(callback));
return true;
}
bool ThumbnailImage::HasData() const {
return static_cast<bool>(image_representation_);
base::BindOnce(&SkBitmapToJPEGData, std::move(bitmap)),
base::BindOnce(&ThumbnailImage::AssignJPEGData,
weak_ptr_factory_.GetWeakPtr()));
}
size_t ThumbnailImage::GetStorageSize() const {
return image_representation_ ? image_representation_->size() : 0;
void ThumbnailImage::RequestThumbnailImage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ConvertJPEGDataToImageSkiaAndNotifyObservers();
}
bool ThumbnailImage::BackedBySameObjectAs(const ThumbnailImage& other) const {
return image_representation_.get() == other.image_representation_.get();
void ThumbnailImage::AssignJPEGData(std::vector<uint8_t> data) {
data_ = base::MakeRefCounted<base::RefCountedData<std::vector<uint8_t>>>(
std::move(data));
ConvertJPEGDataToImageSkiaAndNotifyObservers();
}
// static
ThumbnailImage ThumbnailImage::FromSkBitmap(SkBitmap bitmap) {
ThumbnailImage result;
result.image_representation_ = ThumbnailData::FromSkBitmap(bitmap);
return result;
}
// static
void ThumbnailImage::FromSkBitmapAsync(SkBitmap bitmap,
CreateThumbnailCallback callback) {
base::PostTaskWithTraitsAndReplyWithResult(
bool ThumbnailImage::ConvertJPEGDataToImageSkiaAndNotifyObservers() {
if (!data_)
return false;
return base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailData::FromSkBitmap, bitmap),
base::BindOnce(
[](CreateThumbnailCallback callback,
scoped_refptr<ThumbnailData> representation) {
ThumbnailImage result;
result.image_representation_ = representation;
std::move(callback).Run(result);
},
std::move(callback)));
base::BindOnce(&JPEGDataToImageSkia, data_),
base::BindOnce(&ThumbnailImage::NotifyObservers,
weak_ptr_factory_.GetWeakPtr()));
}
void ThumbnailImage::NotifyObservers(gfx::ImageSkia image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_)
observer.OnThumbnailImageAvailable(image);
}
......@@ -9,64 +9,73 @@
#include "base/callback.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"
// Value type holding thumbnail image data. Data is internally refcounted, so
// copying this object is inexpensive.
//
// 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 {
// Stores compressed thumbnail data for a tab and can vend that data as an
// uncompressed image to observers.
class ThumbnailImage : public base::RefCounted<ThumbnailImage> {
public:
// Callback used by AsImageSkiaAsync. The parameter is the retrieved image.
using AsImageSkiaCallback = base::OnceCallback<void(gfx::ImageSkia)>;
// Observes uncompressed versions of the thumbnail image as they are
// 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.
using CreateThumbnailCallback = base::OnceCallback<void(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;
private:
friend class Delegate;
friend class base::RefCounted<ThumbnailImage>;
// Retrieves the thumbnail data as a renderable image. May involve image
// 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;
virtual ~ThumbnailImage();
// Returns whether there is data stored in the thumbnail.
bool HasData() const;
void AssignJPEGData(std::vector<uint8_t> data);
bool ConvertJPEGDataToImageSkiaAndNotifyObservers();
void NotifyObservers(gfx::ImageSkia image);
// Returns the size (in bytes) required to store the internal representation.
size_t GetStorageSize() const;
Delegate* delegate_;
// Does an address comparison on the refcounted backing store of this object.
// May return false even if the backing stores contain equivalent image data.
bool BackedBySameObjectAs(const ThumbnailImage& other) const;
scoped_refptr<base::RefCountedData<std::vector<uint8_t>>> data_;
// Encodes thumbnail data as ThumbnailRepresentation. May involve an expensive
// operation. Prefer FromSkBitmapAsync() instead.
static ThumbnailImage FromSkBitmap(SkBitmap bitmap);
base::ObserverList<Observer> observers_;
// Converts the bitmap data to the internal ThumbnailRepresentation. May
// involve image encoding or compression on a background thread. Callback is
// called when the operation completes.
static void FromSkBitmapAsync(SkBitmap bitmap,
CreateThumbnailCallback callback);
SEQUENCE_CHECKER(sequence_checker_);
private:
class ThumbnailData;
base::WeakPtrFactory<ThumbnailImage> weak_ptr_factory_{this};
scoped_refptr<ThumbnailData> image_representation_;
DISALLOW_COPY_AND_ASSIGN(ThumbnailImage);
};
#endif // CHROME_BROWSER_UI_THUMBNAILS_THUMBNAIL_IMAGE_H_
......@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/scoped_observer.h"
#include "base/test/scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_skia.h"
......@@ -16,9 +17,54 @@
namespace {
constexpr int kTestBitmapWidth = 200;
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
class ThumbnailImageTest : public testing::Test {
class ThumbnailImageTest : public testing::Test,
public ThumbnailImage::Delegate {
public:
ThumbnailImageTest() = default;
......@@ -30,72 +76,108 @@ class ThumbnailImageTest : public testing::Test {
return bitmap;
}
bool is_being_observed() const { return is_being_observed_; }
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_;
DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest);
};
TEST_F(ThumbnailImageTest, FromSkBitmap) {
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap);
EXPECT_TRUE(image.HasData());
EXPECT_GE(image.GetStorageSize(), 0U);
EXPECT_LE(image.GetStorageSize(), bitmap.computeByteSize());
gfx::ImageSkia image_skia = image.AsImageSkia();
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
TEST_F(ThumbnailImageTest, Add_Remove_Observer) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed());
TestThumbnailImageObserver observer;
image->AddObserver(&observer);
EXPECT_TRUE(image->HasObserver(&observer));
EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer);
EXPECT_FALSE(image->HasObserver(&observer));
EXPECT_FALSE(is_being_observed());
}
TEST_F(ThumbnailImageTest, FromSkBitmapAsync) {
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
ThumbnailImage image;
base::RunLoop run_loop;
ThumbnailImage::FromSkBitmapAsync(
bitmap, base::BindOnce(
[](base::OnceClosure end_closure, ThumbnailImage* dest_image,
ThumbnailImage source_image) {
*dest_image = source_image;
std::move(end_closure).Run();
},
run_loop.QuitClosure(), base::Unretained(&image)));
run_loop.Run();
EXPECT_TRUE(image.HasData());
EXPECT_GE(image.GetStorageSize(), 0U);
EXPECT_LE(image.GetStorageSize(), bitmap.computeByteSize());
gfx::ImageSkia image_skia = image.AsImageSkia();
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
TEST_F(ThumbnailImageTest, Add_Remove_MultipleObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed());
TestThumbnailImageObserver observer;
TestThumbnailImageObserver observer2;
image->AddObserver(&observer);
EXPECT_TRUE(image->HasObserver(&observer));
EXPECT_TRUE(is_being_observed());
image->AddObserver(&observer2);
EXPECT_TRUE(image->HasObserver(&observer2));
EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer);
EXPECT_FALSE(image->HasObserver(&observer));
EXPECT_TRUE(image->HasObserver(&observer2));
EXPECT_TRUE(is_being_observed());
image->RemoveObserver(&observer2);
EXPECT_FALSE(image->HasObserver(&observer2));
EXPECT_FALSE(is_being_observed());
}
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);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap);
EXPECT_TRUE(image.HasData());
image->AssignSkBitmap(bitmap);
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();
EXPECT_TRUE(image_skia.IsThreadSafe());
EXPECT_FALSE(image_skia.isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap);
observer.WaitForImage();
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);
ThumbnailImage image = ThumbnailImage::FromSkBitmap(bitmap);
EXPECT_TRUE(image.HasData());
base::RunLoop run_loop;
gfx::ImageSkia image_skia;
const bool can_convert = image.AsImageSkiaAsync(base::BindOnce(
[](base::OnceClosure end_closure, gfx::ImageSkia* dest_image,
gfx::ImageSkia source_image) {
*dest_image = source_image;
std::move(end_closure).Run();
},
run_loop.QuitClosure(), base::Unretained(&image_skia)));
ASSERT_TRUE(can_convert);
run_loop.Run();
EXPECT_TRUE(image_skia.IsThreadSafe());
EXPECT_FALSE(image_skia.isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight), image_skia.size());
image->AssignSkBitmap(bitmap);
observer.WaitForImage();
TestThumbnailImageObserver observer2;
observer2.scoped_observer()->Add(image.get());
image->RequestThumbnailImage();
observer.WaitForImage();
observer2.WaitForImage();
EXPECT_EQ(2, observer.new_image_count());
EXPECT_EQ(1, observer2.new_image_count());
EXPECT_FALSE(observer2.thumbnail_image().isNull());
EXPECT_EQ(gfx::Size(kTestBitmapWidth, kTestBitmapHeight),
observer2.thumbnail_image().size());
}
......@@ -5,6 +5,8 @@
#ifndef 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/memory/weak_ptr.h"
#include "base/scoped_observer.h"
......@@ -12,70 +14,72 @@
#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_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"
class ThumbnailTabHelper
: public thumbnails::ThumbnailPageObserver,
public content::WebContentsUserData<ThumbnailTabHelper> {
: public content::WebContentsUserData<ThumbnailTabHelper>,
public content::WebContentsObserver,
public viz::mojom::FrameSinkVideoConsumer,
public ThumbnailImage::Delegate {
public:
~ThumbnailTabHelper() override;
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(); }
scoped_refptr<ThumbnailImage> thumbnail() const { return thumbnail_; }
private:
// Loading is treated as a state machine for each new URL, and the state for
// each new URL the current tab loads can only advance as events are received
// (so it is not possible to go from kLoadStarted to kNavigationStarted).
enum class LoadingState : int32_t {
kNone = 0,
kNavigationStarted = 1,
kNavigationFinished = 2,
kLoadStarted = 3,
kLoadFinished = 4
};
class ThumanailImageImpl;
friend class content::WebContentsUserData<ThumbnailTabHelper>;
friend class ThumanailImageImpl;
explicit ThumbnailTabHelper(content::WebContents* contents);
friend class content::WebContentsUserData<ThumbnailTabHelper>;
void StartThumbnailCapture();
void ProcessCapturedThumbnail(base::TimeTicks start_time,
const SkBitmap& bitmap);
void StoreThumbnail(base::TimeTicks start_time, ThumbnailImage thumbnail);
void NotifyTabPreviewChanged();
// ThumbnailImage::Delegate:
void ThumbnailImageBeingObservedChanged(bool is_being_observed) override;
bool ShouldKeepUpdatingThumbnail() const;
void StartVideoCapture();
void StopVideoCapture();
void CaptureThumbnailOnTabSwitch();
void StoreThumbnail(const SkBitmap& bitmap);
content::RenderWidgetHostView* GetView();
void TransitionLoadingState(LoadingState state, const GURL& url);
void ClearThumbnail();
// content::WebContentsObserver:
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
// VisibilityChanged() for more information.
bool view_is_visible_; // set in constructor
// The last known visibility WebContents visibility.
content::Visibility last_visibility_;
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;
GURL current_url_;
// Captures frames from the WebContents while it's hidden. The capturer count
// of the WebContents is incremented/decremented when a capturer is set/unset.
std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer_;
thumbnails::ThumbnailPageEventAdapter adapter_;
ScopedObserver<thumbnails::ThumbnailPageEventAdapter,
thumbnails::ThumbnailPageObserver>
scoped_observer_;
// The thumbnail maintained by this instance.
scoped_refptr<ThumbnailImage> thumbnail_ =
base::MakeRefCounted<ThumbnailImage>(this);
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);
};
......
......@@ -425,6 +425,7 @@ void TabHoverCardBubbleView::UpdateAndShow(Tab* tab) {
void TabHoverCardBubbleView::FadeOutToHide() {
delayed_show_timer_.Stop();
RegisterToThumbnailImageUpdates(nullptr);
if (!widget_->IsVisible())
return;
slide_animation_delegate_->StopAnimation();
......@@ -524,40 +525,56 @@ void TabHoverCardBubbleView::UpdateCardContent(const Tab* tab) {
// If the preview image feature is not enabled, |preview_image_| will be null.
if (preview_image_ && preview_image_->GetVisible()) {
// If there is no valid thumbnail data, blank out the preview, else wait for
// the image data to be decoded and update momentarily.
if (!tab->data().thumbnail.AsImageSkiaAsync(
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.
const SkColor foreground_color = tab->GetThemeProvider()->GetColor(
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_FOREGROUND);
// 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 = tab->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));
}
}
if (tab->data().thumbnail != thumbnail_image_)
ClearPreviewImage();
RegisterToThumbnailImageUpdates(tab->data().thumbnail);
}
}
void TabHoverCardBubbleView::RegisterToThumbnailImageUpdates(
scoped_refptr<ThumbnailImage> thumbnail_image) {
if (thumbnail_image_ == thumbnail_image)
return;
if (thumbnail_image_) {
thumbnail_observer_.Remove(thumbnail_image_.get());
thumbnail_image_.reset();
}
if (thumbnail_image) {
thumbnail_image_ = thumbnail_image;
thumbnail_observer_.Add(thumbnail_image_.get());
thumbnail_image->RequestThumbnailImage();
}
}
void TabHoverCardBubbleView::ClearPreviewImage() {
// Check the no-preview color and size to see if it needs to be
// regenerated. DPI or theme change can cause a regeneration.
const SkColor foreground_color = GetThemeProvider()->GetColor(
ThemeProperties::COLOR_HOVER_CARD_NO_PREVIEW_FOREGROUND);
// 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_->SetImageSize(GetTabHoverCardPreviewImageSize());
preview_image_->SetPreferredSize(GetTabHoverCardPreviewImageSize());
......
......@@ -8,8 +8,10 @@
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
namespace gfx {
......@@ -25,7 +27,8 @@ class Widget;
class Tab;
// Dialog that displays an informational hover card containing page information.
class TabHoverCardBubbleView : public views::BubbleDialogDelegateView {
class TabHoverCardBubbleView : public views::BubbleDialogDelegateView,
public ThumbnailImage::Observer {
public:
explicit TabHoverCardBubbleView(Tab* tab);
......@@ -69,8 +72,15 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView {
// Updates and formats title, domain, and preview image.
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;
void RecordTimeSinceLastSeenMetric(base::TimeDelta elapsed_time);
......@@ -100,6 +110,9 @@ class TabHoverCardBubbleView : public views::BubbleDialogDelegateView {
// Counter used to keey track of the number of tab hover cards seen before a
// tab is selected by mouse press.
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};
......
......@@ -19,9 +19,8 @@ TabRendererData::~TabRendererData() = default;
bool TabRendererData::operator==(const TabRendererData& other) const {
return favicon.BackedBySameObjectAs(other.favicon) &&
thumbnail.BackedBySameObjectAs(other.thumbnail) &&
network_state == other.network_state && title == other.title &&
visible_url == other.visible_url &&
thumbnail == other.thumbnail && network_state == other.network_state &&
title == other.title && visible_url == other.visible_url &&
last_committed_url == other.last_committed_url &&
crashed_status == other.crashed_status &&
incognito == other.incognito && show_icon == other.show_icon &&
......
......@@ -30,7 +30,7 @@ struct TabRendererData {
bool IsCrashed() const;
gfx::ImageSkia favicon;
ThumbnailImage thumbnail;
scoped_refptr<ThumbnailImage> thumbnail;
TabNetworkState network_state = TabNetworkState::kNone;
base::string16 title;
// This corresponds to WebContents::GetVisibleUrl().
......
......@@ -1452,14 +1452,26 @@ void WebContentsImpl::IncrementCapturerCount(const gfx::Size& capture_size) {
}
if (GetVisibility() != Visibility::VISIBLE && !was_captured) {
// Ensure that all views act as if they were visible before capture begins.
// TODO(fdoray): Replace RenderWidgetHostView::WasUnOccluded() with a method
// to explicitly notify the RenderWidgetHostView that capture began.
// https://crbug.com/668690
if (auto* main_view = GetRenderWidgetHostView())
main_view->WasUnOccluded();
// TODO: Share code with WasShown().
SendPageMessage(new PageMsg_WasShown(MSG_ROUTING_NONE));
if (auto* view = GetRenderWidgetHostView()) {
view->Show();
#if defined(OS_MACOSX)
view->SetActive(true);
#endif
}
if (!ShowingInterstitialPage())
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