blob: cb64abd7de7d857d7d7f79155db9159aad480437 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/android/thumbnail/thumbnail_cache.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include "base/android/application_status_listener.h"
#include "base/android/path_utils.h"
#include "base/big_endian.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/android_opengl/etc1/etc1.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "ui/android/resources/ui_resource_provider.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size_conversions.h"
namespace {
const float kApproximationScaleFactor = 4.f;
const base::TimeDelta kCaptureMinRequestTimeMs(
base::TimeDelta::FromMilliseconds(1000));
const int kCompressedKey = 0xABABABAB;
const int kCurrentExtraVersion = 1;
// Indicates whether we prefer to have more free CPU memory over GPU memory.
const bool kPreferCPUMemory = true;
size_t NextPowerOfTwo(size_t x) {
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
size_t RoundUpMod4(size_t x) {
return (x + 3) & ~3;
}
gfx::Size GetEncodedSize(const gfx::Size& bitmap_size, bool supports_npot) {
DCHECK(!bitmap_size.IsEmpty());
if (!supports_npot)
return gfx::Size(NextPowerOfTwo(bitmap_size.width()),
NextPowerOfTwo(bitmap_size.height()));
else
return gfx::Size(RoundUpMod4(bitmap_size.width()),
RoundUpMod4(bitmap_size.height()));
}
template<typename T>
bool ReadBigEndianFromFile(base::File& file, T* out) {
char buffer[sizeof(T)];
if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
return false;
base::ReadBigEndian(buffer, out);
return true;
}
template<typename T>
bool WriteBigEndianToFile(base::File& file, T val) {
char buffer[sizeof(T)];
base::WriteBigEndian(buffer, val);
return file.WriteAtCurrentPos(buffer, sizeof(T)) == sizeof(T);
}
bool ReadBigEndianFloatFromFile(base::File& file, float* out) {
char buffer[sizeof(float)];
if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer))
return false;
#if defined(ARCH_CPU_LITTLE_ENDIAN)
for (size_t i = 0; i < sizeof(float) / 2; i++) {
char tmp = buffer[i];
buffer[i] = buffer[sizeof(float) - 1 - i];
buffer[sizeof(float) - 1 - i] = tmp;
}
#endif
memcpy(out, buffer, sizeof(buffer));
return true;
}
bool WriteBigEndianFloatToFile(base::File& file, float val) {
char buffer[sizeof(float)];
memcpy(buffer, &val, sizeof(buffer));
#if defined(ARCH_CPU_LITTLE_ENDIAN)
for (size_t i = 0; i < sizeof(float) / 2; i++) {
char tmp = buffer[i];
buffer[i] = buffer[sizeof(float) - 1 - i];
buffer[sizeof(float) - 1 - i] = tmp;
}
#endif
return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer);
}
} // anonymous namespace
ThumbnailCache::ThumbnailCache(size_t default_cache_size,
size_t approximation_cache_size,
size_t compression_queue_max_size,
size_t write_queue_max_size,
bool use_approximation_thumbnail)
: file_sequenced_task_runner_(
base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})),
compression_queue_max_size_(compression_queue_max_size),
write_queue_max_size_(write_queue_max_size),
use_approximation_thumbnail_(use_approximation_thumbnail),
compression_tasks_count_(0),
write_tasks_count_(0),
read_in_progress_(false),
cache_(default_cache_size),
approximation_cache_(approximation_cache_size),
ui_resource_provider_(NULL),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
memory_pressure_.reset(new base::MemoryPressureListener(
base::Bind(&ThumbnailCache::OnMemoryPressure, base::Unretained(this))));
}
ThumbnailCache::~ThumbnailCache() {
SetUIResourceProvider(NULL);
}
void ThumbnailCache::SetUIResourceProvider(
ui::UIResourceProvider* ui_resource_provider) {
if (ui_resource_provider_ == ui_resource_provider)
return;
approximation_cache_.Clear();
cache_.Clear();
ui_resource_provider_ = ui_resource_provider;
}
void ThumbnailCache::AddThumbnailCacheObserver(
ThumbnailCacheObserver* observer) {
if (!observers_.HasObserver(observer))
observers_.AddObserver(observer);
}
void ThumbnailCache::RemoveThumbnailCacheObserver(
ThumbnailCacheObserver* observer) {
if (observers_.HasObserver(observer))
observers_.RemoveObserver(observer);
}
void ThumbnailCache::Put(TabId tab_id,
const SkBitmap& bitmap,
float thumbnail_scale) {
if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
return;
if (thumbnail_meta_data_.find(tab_id) == thumbnail_meta_data_.end()) {
DVLOG(1) << "Thumbnail meta data was removed for tab id " << tab_id;
return;
}
base::Time time_stamp = thumbnail_meta_data_[tab_id].capture_time();
std::unique_ptr<Thumbnail> thumbnail = Thumbnail::Create(
tab_id, time_stamp, thumbnail_scale, ui_resource_provider_, this);
thumbnail->SetBitmap(bitmap);
RemoveFromReadQueue(tab_id);
MakeSpaceForNewItemIfNecessary(tab_id);
cache_.Put(tab_id, std::move(thumbnail));
if (use_approximation_thumbnail_) {
std::pair<SkBitmap, float> approximation =
CreateApproximation(bitmap, thumbnail_scale);
std::unique_ptr<Thumbnail> approx_thumbnail = Thumbnail::Create(
tab_id, time_stamp, approximation.second, ui_resource_provider_, this);
approx_thumbnail->SetBitmap(approximation.first);
approximation_cache_.Put(tab_id, std::move(approx_thumbnail));
}
CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale);
}
void ThumbnailCache::Remove(TabId tab_id) {
cache_.Remove(tab_id);
approximation_cache_.Remove(tab_id);
thumbnail_meta_data_.erase(tab_id);
RemoveFromDisk(tab_id);
RemoveFromReadQueue(tab_id);
}
Thumbnail* ThumbnailCache::Get(TabId tab_id,
bool force_disk_read,
bool allow_approximation) {
Thumbnail* thumbnail = cache_.Get(tab_id);
if (thumbnail) {
thumbnail->CreateUIResource();
return thumbnail;
}
if (force_disk_read && primary_tab_id_ != tab_id &&
base::ContainsValue(visible_ids_, tab_id) &&
!base::ContainsValue(read_queue_, tab_id)) {
read_queue_.push_back(tab_id);
ReadNextThumbnail();
}
if (allow_approximation) {
thumbnail = approximation_cache_.Get(tab_id);
if (thumbnail) {
thumbnail->CreateUIResource();
return thumbnail;
}
}
return NULL;
}
void ThumbnailCache::InvalidateThumbnailIfChanged(TabId tab_id,
const GURL& url) {
ThumbnailMetaDataMap::iterator meta_data_iter =
thumbnail_meta_data_.find(tab_id);
if (meta_data_iter == thumbnail_meta_data_.end()) {
thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
} else if (meta_data_iter->second.url() != url) {
Remove(tab_id);
}
}
base::FilePath ThumbnailCache::GetCacheDirectory() {
base::FilePath path;
base::android::GetThumbnailCacheDirectory(&path);
return path;
}
base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) {
base::FilePath path = GetCacheDirectory();
return path.Append(base::IntToString(tab_id));
}
bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id,
const GURL& url) {
base::Time current_time = base::Time::Now();
ThumbnailMetaDataMap::iterator meta_data_iter =
thumbnail_meta_data_.find(tab_id);
if (meta_data_iter != thumbnail_meta_data_.end() &&
meta_data_iter->second.url() == url &&
(current_time - meta_data_iter->second.capture_time()) <
kCaptureMinRequestTimeMs) {
return false;
}
thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url);
return true;
}
void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority,
TabId primary_tab_id) {
bool needs_update = false;
if (primary_tab_id_ != primary_tab_id) {
// The primary screen-filling tab (if any) is not pushed onto the read
// queue, under the assumption that it either has a live layer or will have
// one very soon.
primary_tab_id_ = primary_tab_id;
needs_update = true;
}
size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
if (visible_ids_.size() != ids_size) {
needs_update = true;
} else {
// Early out if called with the same input as last time (We only care
// about the first mCache.MaximumCacheSize() entries).
TabIdList::const_iterator visible_iter = visible_ids_.begin();
TabIdList::const_iterator priority_iter = priority.begin();
while (visible_iter != visible_ids_.end() &&
priority_iter != priority.end()) {
if (*priority_iter != *visible_iter || !cache_.Get(*priority_iter)) {
needs_update = true;
break;
}
visible_iter++;
priority_iter++;
}
}
if (!needs_update)
return;
read_queue_.clear();
visible_ids_.clear();
size_t count = 0;
TabIdList::const_iterator iter = priority.begin();
while (iter != priority.end() && count < ids_size) {
TabId tab_id = *iter;
visible_ids_.push_back(tab_id);
if (!cache_.Get(tab_id) && primary_tab_id_ != tab_id &&
!base::ContainsValue(read_queue_, tab_id))
read_queue_.push_back(tab_id);
iter++;
count++;
}
ReadNextThumbnail();
}
void ThumbnailCache::DecompressThumbnailFromFile(
TabId tab_id,
const base::Callback<void(bool, SkBitmap)>&
post_decompress_callback) {
base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
decompress_task = base::Bind(
&ThumbnailCache::DecompressionTask, post_decompress_callback);
file_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ThumbnailCache::ReadTask, true, tab_id, decompress_task));
}
void ThumbnailCache::RemoveFromDisk(TabId tab_id) {
file_sequenced_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&ThumbnailCache::RemoveFromDiskTask, tab_id));
}
void ThumbnailCache::RemoveFromDiskTask(TabId tab_id) {
base::FilePath file_path = GetFilePath(tab_id);
if (base::PathExists(file_path))
base::DeleteFile(file_path, false);
}
void ThumbnailCache::WriteThumbnailIfNecessary(
TabId tab_id,
sk_sp<SkPixelRef> compressed_data,
float scale,
const gfx::Size& content_size) {
if (write_tasks_count_ >= write_queue_max_size_)
return;
write_tasks_count_++;
base::Callback<void()> post_write_task =
base::Bind(&ThumbnailCache::PostWriteTask, weak_factory_.GetWeakPtr());
file_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ThumbnailCache::WriteTask, tab_id, compressed_data, scale,
content_size, post_write_task));
}
void ThumbnailCache::CompressThumbnailIfNecessary(
TabId tab_id,
const base::Time& time_stamp,
const SkBitmap& bitmap,
float scale) {
if (compression_tasks_count_ >= compression_queue_max_size_) {
RemoveOnMatchedTimeStamp(tab_id, time_stamp);
return;
}
compression_tasks_count_++;
base::Callback<void(sk_sp<SkPixelRef>, const gfx::Size&)>
post_compression_task = base::Bind(&ThumbnailCache::PostCompressionTask,
weak_factory_.GetWeakPtr(),
tab_id,
time_stamp,
scale);
gfx::Size raw_data_size(bitmap.width(), bitmap.height());
gfx::Size encoded_size = GetEncodedSize(
raw_data_size, ui_resource_provider_->SupportsETC1NonPowerOfTwo());
base::PostTaskWithTraits(FROM_HERE,
{base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::Bind(&ThumbnailCache::CompressionTask, bitmap,
encoded_size, post_compression_task));
}
void ThumbnailCache::ReadNextThumbnail() {
if (read_queue_.empty() || read_in_progress_)
return;
TabId tab_id = read_queue_.front();
read_in_progress_ = true;
base::Callback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
post_read_task = base::Bind(
&ThumbnailCache::PostReadTask, weak_factory_.GetWeakPtr(), tab_id);
file_sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ThumbnailCache::ReadTask, false, tab_id, post_read_task));
}
void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
if (cache_.Get(tab_id) || !base::ContainsValue(visible_ids_, tab_id) ||
cache_.size() < cache_.MaximumCacheSize()) {
return;
}
TabId key_to_remove;
bool found_key_to_remove = false;
// 1. Find a cached item not in this list
for (ExpiringThumbnailCache::iterator iter = cache_.begin();
iter != cache_.end();
iter++) {
if (!base::ContainsValue(visible_ids_, iter->first)) {
key_to_remove = iter->first;
found_key_to_remove = true;
break;
}
}
if (!found_key_to_remove) {
// 2. Find the least important id we can remove.
for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
riter != visible_ids_.rend();
riter++) {
if (cache_.Get(*riter)) {
key_to_remove = *riter;
break;
found_key_to_remove = true;
}
}
}
if (found_key_to_remove)
cache_.Remove(key_to_remove);
}
void ThumbnailCache::RemoveFromReadQueue(TabId tab_id) {
TabIdList::iterator read_iter =
std::find(read_queue_.begin(), read_queue_.end(), tab_id);
if (read_iter != read_queue_.end())
read_queue_.erase(read_iter);
}
void ThumbnailCache::OnUIResourcesWereEvicted() {
if (visible_ids_.empty()) {
cache_.Clear();
approximation_cache_.Clear();
} else {
TabId last_tab = visible_ids_.front();
std::unique_ptr<Thumbnail> thumbnail = cache_.Remove(last_tab);
cache_.Clear();
std::unique_ptr<Thumbnail> approximation =
approximation_cache_.Remove(last_tab);
approximation_cache_.Clear();
// Keep the thumbnail for app resume if it wasn't uploaded yet.
if (thumbnail.get() && !thumbnail->ui_resource_id())
cache_.Put(last_tab, std::move(thumbnail));
if (approximation.get() && !approximation->ui_resource_id())
approximation_cache_.Put(last_tab, std::move(approximation));
}
}
void ThumbnailCache::InvalidateCachedThumbnail(Thumbnail* thumbnail) {
DCHECK(thumbnail);
TabId tab_id = thumbnail->tab_id();
cc::UIResourceId uid = thumbnail->ui_resource_id();
Thumbnail* cached_thumbnail = cache_.Get(tab_id);
if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
cache_.Remove(tab_id);
cached_thumbnail = approximation_cache_.Get(tab_id);
if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
approximation_cache_.Remove(tab_id);
}
namespace {
bool WriteToFile(base::File& file,
const gfx::Size& content_size,
const float scale,
sk_sp<SkPixelRef> compressed_data) {
if (!file.IsValid())
return false;
if (!WriteBigEndianToFile(file, kCompressedKey))
return false;
if (!WriteBigEndianToFile(file, content_size.width()))
return false;
if (!WriteBigEndianToFile(file, content_size.height()))
return false;
// Write ETC1 header.
unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
etc1_pkm_format_header(etc1_buffer, compressed_data->width(),
compressed_data->height());
int header_bytes_written = file.WriteAtCurrentPos(
reinterpret_cast<char*>(etc1_buffer), ETC_PKM_HEADER_SIZE);
if (header_bytes_written != ETC_PKM_HEADER_SIZE)
return false;
int data_size = etc1_get_encoded_data_size(compressed_data->width(),
compressed_data->height());
int pixel_bytes_written = file.WriteAtCurrentPos(
reinterpret_cast<char*>(compressed_data->pixels()),
data_size);
if (pixel_bytes_written != data_size)
return false;
if (!WriteBigEndianToFile(file, kCurrentExtraVersion))
return false;
if (!WriteBigEndianFloatToFile(file, 1.f / scale))
return false;
return true;
}
} // anonymous namespace
void ThumbnailCache::WriteTask(TabId tab_id,
sk_sp<SkPixelRef> compressed_data,
float scale,
const gfx::Size& content_size,
const base::Callback<void()>& post_write_task) {
DCHECK(compressed_data);
base::FilePath file_path = GetFilePath(tab_id);
base::File file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
bool success = WriteToFile(file,
content_size,
scale,
compressed_data);
file.Close();
if (!success)
base::DeleteFile(file_path, false);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, post_write_task);
}
void ThumbnailCache::PostWriteTask() {
write_tasks_count_--;
}
void ThumbnailCache::CompressionTask(
SkBitmap raw_data,
gfx::Size encoded_size,
const base::Callback<void(sk_sp<SkPixelRef>, const gfx::Size&)>&
post_compression_task) {
sk_sp<SkPixelRef> compressed_data;
gfx::Size content_size;
if (!raw_data.empty()) {
gfx::Size raw_data_size(raw_data.width(), raw_data.height());
size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
size_t stride = pixel_size * raw_data_size.width();
size_t encoded_bytes =
etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
SkImageInfo info = SkImageInfo::Make(encoded_size.width(),
encoded_size.height(),
kUnknown_SkColorType,
kUnpremul_SkAlphaType);
sk_sp<SkData> etc1_pixel_data(SkData::MakeUninitialized(encoded_bytes));
sk_sp<SkPixelRef> etc1_pixel_ref(
SkMallocPixelRef::MakeWithData(info, 0, std::move(etc1_pixel_data)));
bool success = etc1_encode_image(
reinterpret_cast<unsigned char*>(raw_data.getPixels()),
raw_data_size.width(),
raw_data_size.height(),
pixel_size,
stride,
reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()),
encoded_size.width(),
encoded_size.height());
etc1_pixel_ref->setImmutable();
if (success) {
compressed_data = std::move(etc1_pixel_ref);
content_size = raw_data_size;
}
}
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(post_compression_task, std::move(compressed_data),
content_size));
}
void ThumbnailCache::PostCompressionTask(
TabId tab_id,
const base::Time& time_stamp,
float scale,
sk_sp<SkPixelRef> compressed_data,
const gfx::Size& content_size) {
compression_tasks_count_--;
if (!compressed_data) {
RemoveOnMatchedTimeStamp(tab_id, time_stamp);
return;
}
Thumbnail* thumbnail = cache_.Get(tab_id);
if (thumbnail) {
if (thumbnail->time_stamp() != time_stamp)
return;
thumbnail->SetCompressedBitmap(compressed_data, content_size);
// Don't upload the texture if we are being paused/stopped because
// the context will go away anyways.
if (base::android::ApplicationStatusListener::GetState() ==
base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES) {
thumbnail->CreateUIResource();
}
}
WriteThumbnailIfNecessary(tab_id, std::move(compressed_data), scale,
content_size);
}
namespace {
bool ReadFromFile(base::File& file,
gfx::Size* out_content_size,
float* out_scale,
sk_sp<SkPixelRef>* out_pixels) {
if (!file.IsValid())
return false;
int key = 0;
if (!ReadBigEndianFromFile(file, &key))
return false;
if (key != kCompressedKey)
return false;
int content_width = 0;
if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0)
return false;
int content_height = 0;
if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0)
return false;
out_content_size->SetSize(content_width, content_height);
// Read ETC1 header.
int header_bytes_read = 0;
unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
header_bytes_read = file.ReadAtCurrentPos(
reinterpret_cast<char*>(etc1_buffer),
ETC_PKM_HEADER_SIZE);
if (header_bytes_read != ETC_PKM_HEADER_SIZE)
return false;
if (!etc1_pkm_is_valid(etc1_buffer))
return false;
int raw_width = 0;
raw_width = etc1_pkm_get_width(etc1_buffer);
if (raw_width <= 0)
return false;
int raw_height = 0;
raw_height = etc1_pkm_get_height(etc1_buffer);
if (raw_height <= 0)
return false;
// Do some simple sanity check validation. We can't have thumbnails larger
// than the max display size of the screen. We also can't have etc1 texture
// data larger than the next power of 2 up from that.
gfx::Size display_size =
display::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
int max_dimension = std::max(display_size.width(), display_size.height());
if (content_width > max_dimension
|| content_height > max_dimension
|| static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension)
|| static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) {
return false;
}
int data_size = etc1_get_encoded_data_size(raw_width, raw_height);
sk_sp<SkData> etc1_pixel_data(SkData::MakeUninitialized(data_size));
int pixel_bytes_read = file.ReadAtCurrentPos(
reinterpret_cast<char*>(etc1_pixel_data->writable_data()),
data_size);
if (pixel_bytes_read != data_size)
return false;
SkImageInfo info = SkImageInfo::Make(raw_width,
raw_height,
kUnknown_SkColorType,
kUnpremul_SkAlphaType);
*out_pixels =
SkMallocPixelRef::MakeWithData(info, 0, std::move(etc1_pixel_data));
int extra_data_version = 0;
if (!ReadBigEndianFromFile(file, &extra_data_version))
return false;
*out_scale = 1.f;
if (extra_data_version == 1) {
if (!ReadBigEndianFloatFromFile(file, out_scale))
return false;
if (*out_scale == 0.f)
return false;
*out_scale = 1.f / *out_scale;
}
return true;
}
}// anonymous namespace
void ThumbnailCache::ReadTask(
bool decompress,
TabId tab_id,
const base::Callback<
void(sk_sp<SkPixelRef>, float, const gfx::Size&)>&
post_read_task) {
gfx::Size content_size;
float scale = 0.f;
sk_sp<SkPixelRef> compressed_data;
base::FilePath file_path = GetFilePath(tab_id);
if (base::PathExists(file_path)) {
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
bool valid_contents = ReadFromFile(file,
&content_size,
&scale,
&compressed_data);
file.Close();
if (!valid_contents) {
content_size.SetSize(0, 0);
scale = 0.f;
compressed_data.reset();
base::DeleteFile(file_path, false);
}
}
if (decompress) {
base::PostTaskWithTraits(
FROM_HERE, {base::TaskPriority::BACKGROUND},
base::Bind(post_read_task, std::move(compressed_data), scale,
content_size));
} else {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(post_read_task, std::move(compressed_data), scale,
content_size));
}
}
void ThumbnailCache::PostReadTask(TabId tab_id,
sk_sp<SkPixelRef> compressed_data,
float scale,
const gfx::Size& content_size) {
read_in_progress_ = false;
TabIdList::iterator iter =
std::find(read_queue_.begin(), read_queue_.end(), tab_id);
if (iter == read_queue_.end()) {
ReadNextThumbnail();
return;
}
read_queue_.erase(iter);
if (!cache_.Get(tab_id) && compressed_data) {
ThumbnailMetaDataMap::iterator meta_iter =
thumbnail_meta_data_.find(tab_id);
base::Time time_stamp = base::Time::Now();
if (meta_iter != thumbnail_meta_data_.end())
time_stamp = meta_iter->second.capture_time();
MakeSpaceForNewItemIfNecessary(tab_id);
std::unique_ptr<Thumbnail> thumbnail = Thumbnail::Create(
tab_id, time_stamp, scale, ui_resource_provider_, this);
thumbnail->SetCompressedBitmap(std::move(compressed_data),
content_size);
if (kPreferCPUMemory)
thumbnail->CreateUIResource();
cache_.Put(tab_id, std::move(thumbnail));
NotifyObserversOfThumbnailRead(tab_id);
}
ReadNextThumbnail();
}
void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id) {
for (ThumbnailCacheObserver& observer : observers_)
observer.OnFinishedThumbnailRead(tab_id);
}
void ThumbnailCache::RemoveOnMatchedTimeStamp(TabId tab_id,
const base::Time& time_stamp) {
// We remove the cached version if it matches the tab_id and the time_stamp.
Thumbnail* thumbnail = cache_.Get(tab_id);
Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id);
if ((thumbnail && thumbnail->time_stamp() == time_stamp) ||
(approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) {
Remove(tab_id);
}
return;
}
void ThumbnailCache::DecompressionTask(
const base::Callback<void(bool, SkBitmap)>&
post_decompression_callback,
sk_sp<SkPixelRef> compressed_data,
float scale,
const gfx::Size& content_size) {
SkBitmap raw_data_small;
bool success = false;
if (compressed_data.get()) {
gfx::Size buffer_size =
gfx::Size(compressed_data->width(), compressed_data->height());
SkBitmap raw_data;
raw_data.allocPixels(SkImageInfo::Make(buffer_size.width(),
buffer_size.height(),
kRGBA_8888_SkColorType,
kOpaque_SkAlphaType));
success = etc1_decode_image(
reinterpret_cast<unsigned char*>(compressed_data->pixels()),
reinterpret_cast<unsigned char*>(raw_data.getPixels()),
buffer_size.width(),
buffer_size.height(),
raw_data.bytesPerPixel(),
raw_data.rowBytes());
raw_data.setImmutable();
if (!success) {
// Leave raw_data_small empty for consistency with other failure modes.
} else if (content_size == buffer_size) {
// Shallow copy the pixel reference.
raw_data_small = raw_data;
} else {
// The content size is smaller than the buffer size (likely because of
// a power-of-two rounding), so deep copy the bitmap.
raw_data_small.allocPixels(SkImageInfo::Make(content_size.width(),
content_size.height(),
kRGBA_8888_SkColorType,
kOpaque_SkAlphaType));
SkCanvas small_canvas(raw_data_small);
small_canvas.drawBitmap(raw_data, 0, 0);
raw_data_small.setImmutable();
}
}
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(post_decompression_callback, success, raw_data_small));
}
ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() {
}
ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
const base::Time& current_time,
const GURL& url)
: capture_time_(current_time), url_(url) {
}
std::pair<SkBitmap, float> ThumbnailCache::CreateApproximation(
const SkBitmap& bitmap,
float scale) {
DCHECK(!bitmap.empty());
DCHECK_GT(scale, 0);
float new_scale = 1.f / kApproximationScaleFactor;
gfx::Size dst_size = gfx::ScaleToFlooredSize(
gfx::Size(bitmap.width(), bitmap.height()), new_scale);
SkBitmap dst_bitmap;
dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(),
dst_size.height(),
bitmap.info().colorType(),
bitmap.info().alphaType()));
dst_bitmap.eraseColor(0);
SkCanvas canvas(dst_bitmap);
canvas.scale(new_scale, new_scale);
canvas.drawBitmap(bitmap, 0, 0, NULL);
dst_bitmap.setImmutable();
return std::make_pair(dst_bitmap, new_scale * scale);
}
void ThumbnailCache::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
cache_.Clear();
approximation_cache_.Clear();
}
}