| Index: chrome/browser/android/thumbnail_cache.cc
|
| diff --git a/chrome/browser/android/thumbnail_cache.cc b/chrome/browser/android/thumbnail_cache.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2bb8cf742e90a0cf68ba7e568058de7c1d93c033
|
| --- /dev/null
|
| +++ b/chrome/browser/android/thumbnail_cache.cc
|
| @@ -0,0 +1,1357 @@
|
| +// 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_cache.h"
|
| +
|
| +#include <algorithm>
|
| +#include <cmath>
|
| +
|
| +#include "base/file_util.h"
|
| +#include "base/files/file.h"
|
| +#include "base/files/file_enumerator.h"
|
| +#include "base/files/file_path.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/time.h"
|
| +#include "chrome/browser/android/tab_thumbnail_provider.h"
|
| +#include "content/public/browser/android/content_view_core.h"
|
| +#include "content/public/browser/android/ui_resource_provider.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/render_view_host.h"
|
| +#include "content/public/browser/render_widget_host_view.h"
|
| +#include "content/public/browser/web_contents.h"
|
| +#include "skia/ext/refptr.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/gfx/geometry/size_conversions.h"
|
| +
|
| +namespace {
|
| +
|
| +const size_t kMaxReadbacks = 1;
|
| +const bool kDropCachedNTPOnLowMemory = true;
|
| +const float kApproximationScaleFactor = 4.f;
|
| +const bool kEnableCompression = true;
|
| +const base::TimeDelta kCaptureMinRequestTimeMs(
|
| + base::TimeDelta::FromMilliseconds(1000));
|
| +const int kCompressedKey = 0xABABABAB;
|
| +const int kDecompressedKey = 0xCDCDCDCD;
|
| +
|
| +// ETC1 texture sizes are multiples of four.
|
| +size_t NextETC1Size(size_t s) {
|
| + return (s / 4 + (s % 4 ? 1 : 0)) * 4;
|
| +}
|
| +
|
| +gfx::Size GetEncodedSize(gfx::Size bitmap_size) {
|
| + return gfx::Size(NextETC1Size(bitmap_size.width()),
|
| + NextETC1Size(bitmap_size.height()));
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +ThumbnailCache::ThumbnailCache(const std::string& disk_cache_path_str,
|
| + 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_thumbnails,
|
| + float thumbnail_scale)
|
| + : ui_resource_provider_(NULL),
|
| + disk_cache_path_(disk_cache_path_str),
|
| + compression_queue_max_size_(compression_queue_max_size),
|
| + write_queue_max_size_(write_queue_max_size),
|
| + cache_(default_cache_size),
|
| + approximation_cache_(approximation_cache_size),
|
| + use_approximation_thumbnails_(use_approximation_thumbnails),
|
| + thumbnail_scale_(thumbnail_scale),
|
| + compression_thread_("thumbnail_compression"),
|
| + weak_factory_(this) {
|
| + compression_thread_.Start();
|
| +}
|
| +
|
| +ThumbnailCache::~ThumbnailCache() {
|
| + compression_thread_.Stop();
|
| + SetUIResourceProvider(NULL);
|
| +}
|
| +
|
| +void ThumbnailCache::SetUIResourceProvider(
|
| + content::UIResourceProvider* ui_resource_provider) {
|
| + if (ui_resource_provider_ == ui_resource_provider)
|
| + return;
|
| +
|
| + // Clean up the UI resources.
|
| + for (ExpiringThumbnailCache::iterator iter = approximation_cache_.begin();
|
| + iter != approximation_cache_.end();
|
| + iter++) {
|
| + scoped_refptr<Thumbnail> thumbnail = iter->second;
|
| + thumbnail->CleanupThumbnail();
|
| + }
|
| +
|
| + for (ExpiringThumbnailCache::iterator iter = cache_.begin();
|
| + iter != cache_.end();
|
| + iter++) {
|
| + scoped_refptr<Thumbnail> thumbnail = iter->second;
|
| + thumbnail->CleanupThumbnail();
|
| + }
|
| +
|
| + if (ui_resource_provider_)
|
| + ui_resource_provider_->RemoveListener(this);
|
| +
|
| + ui_resource_provider_ = ui_resource_provider;
|
| +
|
| + if (ui_resource_provider_)
|
| + ui_resource_provider_->AddListener(this);
|
| +}
|
| +
|
| +void ThumbnailCache::CacheInTab(TabId tab_id) {
|
| + GetThumbnail(tab_id, true);
|
| +}
|
| +
|
| +void ThumbnailCache::AddThumbnailChangeListener(
|
| + ThumbnailChangeListener* listener) {
|
| + if (thumbnail_change_listeners_.find(listener) ==
|
| + thumbnail_change_listeners_.end()) {
|
| + thumbnail_change_listeners_.insert(listener);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveThumbnailChangeListener(
|
| + ThumbnailChangeListener* listener) {
|
| + ThumbnailChangeListenerSet::iterator iter =
|
| + thumbnail_change_listeners_.find(listener);
|
| + if (iter != thumbnail_change_listeners_.end())
|
| + thumbnail_change_listeners_.erase(iter);
|
| +}
|
| +
|
| +scoped_refptr<ThumbnailCache::Thumbnail> ThumbnailCache::GetThumbnail(
|
| + TabId id) {
|
| + return GetThumbnail(id, false);
|
| +}
|
| +
|
| +scoped_refptr<ThumbnailCache::Thumbnail> ThumbnailCache::GetThumbnail(
|
| + TabId id,
|
| + bool cache_in_if_missing) {
|
| + scoped_refptr<Thumbnail> thumbnail = cache_.Get(id);
|
| +
|
| + if (!thumbnail) {
|
| + if (cache_in_if_missing) {
|
| + AddIdToVisibleIds(id);
|
| + ReadNextThumbnail(id);
|
| + }
|
| + thumbnail = approximation_cache_.Get(id);
|
| + }
|
| +
|
| + return thumbnail;
|
| +}
|
| +
|
| +bool ThumbnailCache::CanReadTabContent(const TabThumbnailProvider* tab) {
|
| + content::ContentViewCore* view = tab->GetContentViewCore();
|
| + return view &&
|
| + view->GetWebContents()->GetRenderViewHost()->CanCopyFromBackingStore();
|
| +}
|
| +
|
| +void ThumbnailCache::CacheTabThumbnail(const TabThumbnailProvider* tab) {
|
| + TabId tab_id = tab->GetAndroidId();
|
| + if (pending_thumbnail_readbacks_.find(tab_id) !=
|
| + pending_thumbnail_readbacks_.end() ||
|
| + pending_thumbnail_readbacks_.size() >= kMaxReadbacks) {
|
| + return;
|
| + }
|
| +
|
| + InvalidateIfChanged(tab);
|
| + if (CheckAndUpdateThumbnailMetaData(tab, false)) {
|
| + base::Callback<void(int, const ThumbnailBitmap&)> end_callback = base::Bind(
|
| + &ThumbnailCache::EndCacheTabThumbnail, weak_factory_.GetWeakPtr());
|
| + scoped_refptr<TabReadbackRequest> readback =
|
| + make_scoped_refptr(new TabReadbackRequest(
|
| + tab_id, tab->GetContentViewCore(), thumbnail_scale_, end_callback));
|
| + pending_thumbnail_readbacks_[tab_id] = readback;
|
| + readback->Run();
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::CacheTabThumbnailWithBitmap(
|
| + const TabThumbnailProvider* tab,
|
| + const SkBitmap& bitmap,
|
| + float thumbnail_scale) {
|
| + bool is_native_page = true;
|
| + TabId tab_id = tab->GetAndroidId();
|
| + InvalidateIfChanged(tab);
|
| + if (CheckAndUpdateThumbnailMetaData(tab, is_native_page))
|
| + PutThumbnail(tab_id, ThumbnailBitmap(bitmap, thumbnail_scale));
|
| +}
|
| +
|
| +void ThumbnailCache::EndCacheTabThumbnail(TabId tab_id,
|
| + const ThumbnailBitmap& bitmap) {
|
| + bool is_native_page = false;
|
| + TabReadbackRequestMap::iterator readback_iter =
|
| + pending_thumbnail_readbacks_.find(tab_id);
|
| + if (readback_iter != pending_thumbnail_readbacks_.end()) {
|
| + scoped_refptr<TabReadbackRequest> readback = readback_iter->second;
|
| + pending_thumbnail_readbacks_.erase(tab_id);
|
| + if (!readback->drop_after_readback() && bitmap.IsValid()) {
|
| + PutThumbnail(tab_id, bitmap);
|
| + }
|
| + NotifyListenersOfThumbnailChange(tab_id, is_native_page);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::InvalidateIfChanged(const TabThumbnailProvider* tab) {
|
| + TabId tab_id = tab->GetAndroidId();
|
| + ThumbnailMetaDataMap::iterator meta_data_iter =
|
| + thumbnail_meta_data_.find(tab_id);
|
| + if (meta_data_iter == thumbnail_meta_data_.end()) {
|
| + // We need this case so we have metadata when loading the thumbnail from
|
| + // disk. Note that the time stamp is 0 to force invalidation when a new
|
| + // thumbnail is requested to be captured.
|
| + thumbnail_meta_data_[tab_id] =
|
| + ThumbnailMetaData(base::Time(), tab->GetURL());
|
| + } else if (meta_data_iter->second.url() != tab->GetURL()) {
|
| + Remove(tab_id);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::PutThumbnail(TabId tab_id,
|
| + const ThumbnailBitmap& thumbnail_bitmap) {
|
| + if (!ui_resource_provider_ || !thumbnail_bitmap.IsValid())
|
| + return;
|
| +
|
| + scoped_refptr<Thumbnail> thumbnail =
|
| + make_scoped_refptr(new Thumbnail(tab_id,
|
| + thumbnail_bitmap.bitmap(),
|
| + thumbnail_bitmap.scale(),
|
| + ui_resource_provider_));
|
| + PutThumbnail(tab_id, thumbnail_bitmap, thumbnail);
|
| +}
|
| +
|
| +void ThumbnailCache::PutThumbnail(TabId tab_id,
|
| + const ThumbnailBitmap& thumbnail_bitmap,
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + if (!ui_resource_provider_ || !thumbnail || !thumbnail_bitmap.IsValid())
|
| + return;
|
| +
|
| + scoped_refptr<Thumbnail> approx_thumbnail = NULL;
|
| + ThumbnailBitmap approx_thumbnail_bitmap;
|
| + if (use_approximation_thumbnails_)
|
| + approx_thumbnail_bitmap = thumbnail_bitmap.CreateApproximation();
|
| +
|
| + if (approx_thumbnail_bitmap.IsValid()) {
|
| + approx_thumbnail =
|
| + make_scoped_refptr(new ApproxThumbnail(tab_id,
|
| + approx_thumbnail_bitmap.bitmap(),
|
| + approx_thumbnail_bitmap.scale(),
|
| + ui_resource_provider_));
|
| + }
|
| +
|
| + AddIdToVisibleIds(tab_id);
|
| + RemoveFromQueues(tab_id);
|
| + MakeSpaceForNewItemIfNecessary(tab_id);
|
| + CleanupThumbnail(cache_.Get(tab_id));
|
| + cache_.Put(tab_id, thumbnail);
|
| +
|
| + if (approx_thumbnail) {
|
| + CleanupThumbnail(approximation_cache_.Get(tab_id));
|
| + approximation_cache_.Put(tab_id, approx_thumbnail);
|
| + }
|
| +
|
| + CompressThumbnailIfNecessary(thumbnail);
|
| + WriteThumbnailIfNecessary(thumbnail);
|
| + NotifyListenersOfThumbnailChange(tab_id, true);
|
| +}
|
| +
|
| +bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(
|
| + const TabThumbnailProvider* tab,
|
| + bool is_native_page) {
|
| + if (!is_native_page && !CanReadTabContent(tab))
|
| + return false;
|
| +
|
| + // TODO(tedchoc): Add check to see if the tab has actually drawn content.
|
| + // Then add a draw counter to see if the content has actually changed since
|
| + // last thumbnail capture.
|
| + TabId tab_id = tab->GetAndroidId();
|
| + ThumbnailMetaDataMap::iterator meta_data_iter =
|
| + thumbnail_meta_data_.find(tab_id);
|
| + if (meta_data_iter != thumbnail_meta_data_.end() &&
|
| + meta_data_iter->second.url() == tab->GetURL() &&
|
| + (CurrentTime() - meta_data_iter->second.capture_time()) <
|
| + kCaptureMinRequestTimeMs) {
|
| + return false;
|
| + }
|
| +
|
| + thumbnail_meta_data_[tab_id] =
|
| + ThumbnailMetaData(CurrentTime(), tab->GetURL());
|
| + return true;
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromCache(ExpiringThumbnailCache& cache,
|
| + const TabIdList& tab_ids) {
|
| + for (TabIdList::const_iterator iter = tab_ids.begin(); iter != tab_ids.end();
|
| + iter++) {
|
| + cache.Remove(*iter);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveThumbnailsAndScheduleReload() {
|
| + TabIdList modified_entries;
|
| + TabIdList remove_from_cache;
|
| + for (ExpiringThumbnailCache::iterator iter = approximation_cache_.begin();
|
| + iter != approximation_cache_.end();
|
| + iter++) {
|
| + TabId tab_id = iter->first;
|
| + scoped_refptr<Thumbnail> thumbnail = iter->second;
|
| + thumbnail->CleanupThumbnail();
|
| +
|
| + if (!thumbnail->AttemptToScheduleRebuildFromData())
|
| + remove_from_cache.push_back(tab_id);
|
| + modified_entries.push_back(tab_id);
|
| + }
|
| +
|
| + read_queue_.clear();
|
| + RemoveFromCache(approximation_cache_, remove_from_cache);
|
| + remove_from_cache.clear();
|
| +
|
| + for (ExpiringThumbnailCache::iterator iter = cache_.begin();
|
| + iter != cache_.end();
|
| + iter++) {
|
| + TabId tab_id = iter->first;
|
| + scoped_refptr<Thumbnail> thumbnail = iter->second;
|
| + thumbnail->CleanupThumbnail();
|
| +
|
| + if (!thumbnail->AttemptToScheduleRebuildFromData())
|
| + remove_from_cache.push_back(tab_id);
|
| + modified_entries.push_back(tab_id);
|
| + }
|
| +
|
| + RemoveFromCache(cache_, remove_from_cache);
|
| + remove_from_cache.clear();
|
| +
|
| + for (TabIdList::iterator iter = modified_entries.begin();
|
| + iter != modified_entries.end();
|
| + iter++) {
|
| + NotifyListenersOfThumbnailChange(*iter, false);
|
| + }
|
| +
|
| + ReadNextThumbnail();
|
| +
|
| + TabIdList last_visible_ids = last_visible_ids_;
|
| + last_visible_ids_.clear();
|
| + UpdateVisibleIds(last_visible_ids);
|
| +}
|
| +
|
| +void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority) {
|
| + if (priority.empty()) {
|
| + last_visible_ids_.clear();
|
| + return;
|
| + }
|
| +
|
| + size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
|
| + if (last_visible_ids_.size() == ids_size) {
|
| + // Early out if called with the same input as last time (We only care
|
| + // about the first mCache.MaximumCacheSize() entries).
|
| + bool lists_differ = false;
|
| + for (TabIdList::const_iterator visible_iter = last_visible_ids_.begin(),
|
| + priority_iter = priority.begin();
|
| + visible_iter != last_visible_ids_.end() &&
|
| + priority_iter != priority.end();
|
| + visible_iter++, priority_iter++) {
|
| + if (*priority_iter != *visible_iter) {
|
| + lists_differ = true;
|
| + break;
|
| + }
|
| + }
|
| + if (!lists_differ)
|
| + return;
|
| + }
|
| +
|
| + last_visible_ids_.clear();
|
| + visible_ids_.clear();
|
| + size_t count = 0;
|
| + for (TabIdList::const_iterator iter = priority.begin();
|
| + iter != priority.end() && count < ids_size;
|
| + iter++, count++) {
|
| + TabId tab_id = *iter;
|
| + last_visible_ids_.push_back(tab_id);
|
| + visible_ids_.push_back(tab_id);
|
| + }
|
| +
|
| + read_queue_.clear();
|
| +
|
| + count = 0;
|
| + for (TabIdList::const_iterator iter = priority.begin();
|
| + iter != priority.end() && count < ids_size;
|
| + iter++, count++) {
|
| + TabId tab_id = *iter;
|
| + if (!cache_.Get(tab_id) &&
|
| + std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
|
| + read_queue_.end()) {
|
| + read_queue_.push_back(tab_id);
|
| + }
|
| + }
|
| +
|
| + ReadNextThumbnail();
|
| +}
|
| +
|
| +void ThumbnailCache::Remove(TabId tab_id) {
|
| + TabReadbackRequestMap::iterator readback_iter =
|
| + pending_thumbnail_readbacks_.find(tab_id);
|
| + if (readback_iter != pending_thumbnail_readbacks_.end())
|
| + readback_iter->second->SetDropAfterReadback(true);
|
| +
|
| + scoped_refptr<Thumbnail> thumbnail = cache_.Remove(tab_id);
|
| + CleanupThumbnail(thumbnail);
|
| +
|
| + scoped_refptr<Thumbnail> approx_thumbnail =
|
| + approximation_cache_.Remove(tab_id);
|
| + CleanupThumbnail(approx_thumbnail);
|
| +
|
| + thumbnail_meta_data_.erase(tab_id);
|
| + RemoveFromDisk(tab_id);
|
| + RemoveFromQueues(tab_id);
|
| +
|
| + if (thumbnail || approx_thumbnail)
|
| + NotifyListenersOfThumbnailChange(tab_id, false);
|
| +}
|
| +
|
| +void ThumbnailCache::HandleLowMemory(bool consider_gpu_memory) {
|
| + HandleLowMemoryOnQueue(compression_queue_, consider_gpu_memory);
|
| + HandleLowMemoryOnQueue(write_queue_, consider_gpu_memory);
|
| +
|
| + read_queue_.clear();
|
| +
|
| + size_t cached_tabs_free_count = 0;
|
| + if (cache_.size() > 0) {
|
| + cached_tabs_free_count = std::max(cache_.size() / 2, 1u);
|
| + for (size_t i = 0; i < cached_tabs_free_count; ++i) {
|
| + RemoveThumbnailFromCache();
|
| + }
|
| + }
|
| +}
|
| +
|
| +size_t ThumbnailCache::HandleLowMemoryOnQueue(ThumbnailQueue& queue,
|
| + bool consider_gpu_memory) {
|
| + size_t thumbnails_dropped = 0;
|
| + ThumbnailQueue next_queue;
|
| + for (ThumbnailQueue::iterator iter = queue.begin(); iter != queue.end();
|
| + iter++) {
|
| + scoped_refptr<Thumbnail> thumbnail = *iter;
|
| + TabId tab_id = thumbnail->tab_id();
|
| + if (!consider_gpu_memory && thumbnail.get() == cache_.Get(tab_id).get())
|
| + RebuildThumbnail(thumbnail, true);
|
| +
|
| + CleanupThumbnail(thumbnail);
|
| + ++thumbnails_dropped;
|
| + }
|
| +
|
| + queue = next_queue;
|
| +
|
| + return thumbnails_dropped;
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromQueues(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);
|
| +
|
| + ThumbnailQueue new_write_queue;
|
| + for (ThumbnailQueue::iterator iter = write_queue_.begin();
|
| + iter != write_queue_.end();
|
| + iter++) {
|
| + scoped_refptr<Thumbnail> write_thumbnail = *iter;
|
| + if (write_thumbnail->tab_id() == tab_id) {
|
| + write_thumbnail->CleanupCPUData(true);
|
| + CleanupThumbnail(write_thumbnail);
|
| + } else {
|
| + new_write_queue.push_back(write_thumbnail);
|
| + }
|
| + }
|
| + write_queue_ = new_write_queue;
|
| +
|
| + ThumbnailQueue new_compression_queue;
|
| + for (ThumbnailQueue::iterator iter = compression_queue_.begin();
|
| + iter != compression_queue_.end();
|
| + iter++) {
|
| + scoped_refptr<Thumbnail> compress_thumbnail = *iter;
|
| + if (compress_thumbnail->tab_id() == tab_id) {
|
| + compress_thumbnail->CleanupCPUData(true);
|
| + CleanupThumbnail(compress_thumbnail);
|
| + } else {
|
| + new_compression_queue.push_back(compress_thumbnail);
|
| + }
|
| + }
|
| + compression_queue_ = new_compression_queue;
|
| +}
|
| +
|
| +bool ThumbnailCache::WriteThumbnailIfNecessary(
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + if (!thumbnail->DataWriteRequired())
|
| + return false;
|
| +
|
| + if (RemoveDuplicateIdsFromQueueHelper(write_queue_, thumbnail))
|
| + return true;
|
| +
|
| + if (write_queue_.size() > write_queue_max_size_) {
|
| + CleanupThumbnail(write_queue_.front());
|
| + write_queue_.pop_front();
|
| + }
|
| +
|
| + write_queue_.push_back(thumbnail);
|
| + WriteNextThumbnail();
|
| + return true;
|
| +}
|
| +
|
| +bool ThumbnailCache::CompressThumbnailIfNecessary(
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + if (!thumbnail->CompressionRequired())
|
| + return false;
|
| +
|
| + if (RemoveDuplicateIdsFromQueueHelper(compression_queue_, thumbnail))
|
| + return true;
|
| +
|
| + if (compression_queue_.size() > compression_queue_max_size_) {
|
| + CleanupThumbnail(compression_queue_.front());
|
| + compression_queue_.pop_front();
|
| + }
|
| +
|
| + compression_queue_.push_back(thumbnail);
|
| + CompressNextThumbnail();
|
| + return true;
|
| +}
|
| +
|
| +bool ThumbnailCache::RemoveDuplicateIdsFromQueueHelper(
|
| + ThumbnailQueue& queue,
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + bool found = false;
|
| + ThumbnailQueue remove_queue;
|
| + for (ThumbnailQueue::iterator iter = queue.begin(); iter != queue.end();
|
| + iter++) {
|
| + if (iter->get() == thumbnail.get())
|
| + found = true;
|
| + if ((*iter)->tab_id() == thumbnail->tab_id()) {
|
| + remove_queue.push_back(thumbnail);
|
| + CleanupThumbnail(thumbnail);
|
| + }
|
| + }
|
| + return found;
|
| +}
|
| +
|
| +void ThumbnailCache::WriteNextThumbnail() {
|
| + if (!write_queue_.empty()) {
|
| + scoped_refptr<Thumbnail> thumbnail = write_queue_.front();
|
| + write_queue_.pop_front();
|
| +
|
| + if (!thumbnail->IsValid())
|
| + return;
|
| +
|
| + // Make a copy of the thumbnail for write to disk. This copy increases the
|
| + // ref-count of the pixels that the thumbnail is holding on to. We do this
|
| + // to avoid concurrent access issues.
|
| + scoped_refptr<Thumbnail> task_thumbnail =
|
| + make_scoped_refptr(new Thumbnail(*thumbnail));
|
| +
|
| + base::Closure write_task = base::Bind(&ThumbnailCache::WriteTask,
|
| + GetFilePath(task_thumbnail->tab_id()),
|
| + task_thumbnail);
|
| + base::Closure post_write_task = base::Bind(&ThumbnailCache::PostWriteTask,
|
| + weak_factory_.GetWeakPtr(),
|
| + task_thumbnail);
|
| + content::BrowserThread::PostTaskAndReply(
|
| + content::BrowserThread::FILE, FROM_HERE, write_task, post_write_task);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::WriteTask(const base::FilePath& file_path,
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + DCHECK(thumbnail);
|
| + DCHECK(thumbnail->IsValid());
|
| + base::File file(file_path,
|
| + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
|
| + thumbnail->WriteThumbnailToFile(file);
|
| + file.Close();
|
| +}
|
| +
|
| +void ThumbnailCache::PostWriteTask(scoped_refptr<Thumbnail> thumbnail) {
|
| + DCHECK(thumbnail);
|
| + // If the write has failed, then this thumbnail is invalidated, we delete the
|
| + // associated file. The tab Id should still be valid.
|
| + TabId tab_id = thumbnail->tab_id();
|
| + if (!thumbnail->IsValid())
|
| + RemoveCorruptThumbnailSource(tab_id);
|
| + CleanupThumbnail(GetThumbnail(tab_id));
|
| + WriteNextThumbnail();
|
| +}
|
| +
|
| +void ThumbnailCache::ReadNextThumbnail(TabId id) {
|
| + read_queue_.clear();
|
| + if (std::find(read_queue_.begin(), read_queue_.end(), id) ==
|
| + read_queue_.end())
|
| + read_queue_.push_back(id);
|
| + ReadNextThumbnail();
|
| +}
|
| +
|
| +void ThumbnailCache::ReadNextThumbnail() {
|
| + if (!read_queue_.empty()) {
|
| + TabId tab_id = read_queue_.front();
|
| + // Create a thumbnail to hold the content of the read.
|
| + scoped_refptr<Thumbnail> thumbnail =
|
| + make_scoped_refptr(new Thumbnail(tab_id, ui_resource_provider_));
|
| +
|
| + base::Closure read_task = base::Bind(
|
| + &ThumbnailCache::ReadTask, GetFilePath(thumbnail->tab_id()), thumbnail);
|
| + base::Closure post_read_task = base::Bind(
|
| + &ThumbnailCache::PostReadTask, weak_factory_.GetWeakPtr(), thumbnail);
|
| + content::BrowserThread::PostTaskAndReply(
|
| + content::BrowserThread::FILE, FROM_HERE, read_task, post_read_task);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::ReadTask(const base::FilePath& file_path,
|
| + scoped_refptr<Thumbnail> thumbnail) {
|
| + DCHECK(thumbnail);
|
| + if (!base::PathExists(file_path))
|
| + return;
|
| +
|
| + base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
|
| + DCHECK(file.IsValid());
|
| + thumbnail->ReadThumbnailFromFile(file);
|
| + file.Close();
|
| +}
|
| +
|
| +void ThumbnailCache::PostReadTask(scoped_refptr<Thumbnail> thumbnail) {
|
| + DCHECK(thumbnail);
|
| + TabIdList::iterator iter =
|
| + std::find(read_queue_.begin(), read_queue_.end(), thumbnail->tab_id());
|
| + if (iter == read_queue_.end())
|
| + return;
|
| +
|
| + read_queue_.erase(iter);
|
| +
|
| + // If the read has failed, then this thumbnail is invalidated, we delete the
|
| + // associated file. The tab Id should still be valid.
|
| + TabId tab_id = thumbnail->tab_id();
|
| + if (!thumbnail->IsValid()) {
|
| + RemoveCorruptThumbnailSource(tab_id);
|
| + ReadNextThumbnail();
|
| + return;
|
| + }
|
| +
|
| + // TODO(powei): We should be able to remove this. All on-disk thumbnails
|
| + // are compressed and so are not amenable to approximation.
|
| + scoped_refptr<Thumbnail> approx_thumbnail;
|
| + if (!approximation_cache_.Contains(tab_id)) {
|
| + SkBitmap raw_data = thumbnail->raw_data();
|
| + if (!raw_data.empty() && use_approximation_thumbnails_) {
|
| + ThumbnailBitmap thumbnail_bitmap(raw_data, thumbnail->scale());
|
| + ThumbnailBitmap approx_thumbnail_bitmap =
|
| + thumbnail_bitmap.CreateApproximation();
|
| + if (approx_thumbnail_bitmap.IsValid()) {
|
| + approx_thumbnail = make_scoped_refptr(
|
| + new ApproxThumbnail(tab_id,
|
| + approx_thumbnail_bitmap.bitmap(),
|
| + approx_thumbnail_bitmap.scale(),
|
| + ui_resource_provider_));
|
| + }
|
| + }
|
| + }
|
| + MakeSpaceForNewItemIfNecessary(tab_id);
|
| + scoped_refptr<Thumbnail> old_thumbnail = cache_.Remove(tab_id);
|
| + CleanupThumbnail(old_thumbnail);
|
| + cache_.Put(tab_id, thumbnail);
|
| + if (approx_thumbnail) {
|
| + scoped_refptr<Thumbnail> old_approx_thumbnail =
|
| + approximation_cache_.Remove(tab_id);
|
| + CleanupThumbnail(old_approx_thumbnail);
|
| + approximation_cache_.Put(tab_id, approx_thumbnail);
|
| + }
|
| +
|
| + RebuildThumbnail(thumbnail, false);
|
| + NotifyListenersOfThumbnailChange(tab_id, true);
|
| +
|
| + // TODO(powei): We should be able to remove this. All on-disk thumbnails are
|
| + // compressed.
|
| + if (thumbnail->CompressionRequired()) {
|
| + compression_queue_.push_back(thumbnail);
|
| + CompressNextThumbnail();
|
| + }
|
| +
|
| + ReadNextThumbnail();
|
| +}
|
| +
|
| +void ThumbnailCache::CompressNextThumbnail() {
|
| + if (!compression_queue_.empty()) {
|
| + scoped_refptr<Thumbnail> thumbnail = compression_queue_.front();
|
| + compression_queue_.pop_front();
|
| +
|
| + // Make a copy of the thumbnail for compression. This copy increases the
|
| + // ref-count of the pixels that the thumbnail is holding on to. We do this
|
| + // to avoid concurrent access issues.
|
| + scoped_refptr<Thumbnail> task_thumbnail =
|
| + make_scoped_refptr(new Thumbnail(*thumbnail));
|
| +
|
| + base::Closure compression_task =
|
| + base::Bind(&ThumbnailCache::CompressionTask, task_thumbnail);
|
| + base::Closure post_compression_task =
|
| + base::Bind(&ThumbnailCache::PostCompressionTask,
|
| + weak_factory_.GetWeakPtr(),
|
| + task_thumbnail);
|
| +
|
| + DCHECK(compression_thread_.message_loop_proxy());
|
| + compression_thread_.message_loop_proxy()->PostTaskAndReply(
|
| + FROM_HERE, compression_task, post_compression_task);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::CompressionTask(scoped_refptr<Thumbnail> thumbnail) {
|
| + thumbnail->Compress();
|
| +}
|
| +
|
| +void ThumbnailCache::PostCompressionTask(
|
| + scoped_refptr<Thumbnail> compressed_thumbnail) {
|
| + DCHECK(compressed_thumbnail);
|
| + TabId tab_id = compressed_thumbnail->tab_id();
|
| + if (!compressed_thumbnail->HasCompressedRawData()) {
|
| + // Compression has failed and we clean up.
|
| + CleanupThumbnail(cache_.Remove(tab_id));
|
| + CleanupThumbnail(compressed_thumbnail);
|
| + NotifyListenersOfThumbnailChange(tab_id, false);
|
| + } else {
|
| + // The thumbnail in |cache_| is not the same reference as
|
| + // |compressed_thumbnail|. Replace the one in |cache_|.
|
| + bool rebuild_thumbnail = false;
|
| + if (cache_.Get(tab_id)) {
|
| + cache_.Put(tab_id, compressed_thumbnail);
|
| + rebuild_thumbnail = true;
|
| + }
|
| +
|
| + if (rebuild_thumbnail)
|
| + RebuildThumbnail(compressed_thumbnail, false);
|
| +
|
| + WriteThumbnailIfNecessary(compressed_thumbnail);
|
| + CleanupThumbnail(compressed_thumbnail);
|
| + NotifyListenersOfThumbnailChange(tab_id, true);
|
| + }
|
| + CompressNextThumbnail();
|
| +}
|
| +
|
| +void ThumbnailCache::RebuildThumbnail(scoped_refptr<Thumbnail> thumbnail,
|
| + bool force_cleanup_cpu_data) {
|
| + if (!thumbnail)
|
| + return;
|
| +
|
| + thumbnail->RebuildThumbnail();
|
| + thumbnail->CleanupCPUData(force_cleanup_cpu_data);
|
| +}
|
| +
|
| +bool ThumbnailCache::ShouldCleanupThumbnail(
|
| + scoped_refptr<Thumbnail> thumbnail,
|
| + const ExpiringThumbnailCache* forced_source) {
|
| + scoped_refptr<Thumbnail> cache_entry = cache_.Get(thumbnail->tab_id());
|
| + scoped_refptr<Thumbnail> approx_entry =
|
| + approximation_cache_.Get(thumbnail->tab_id());
|
| + return thumbnail->is_approximation() ||
|
| + ((cache_entry.get() != thumbnail.get() || forced_source == &cache_) &&
|
| + (approx_entry.get() != thumbnail.get() ||
|
| + forced_source == &approximation_cache_) &&
|
| + std::find(write_queue_.begin(), write_queue_.end(), thumbnail) ==
|
| + write_queue_.end() &&
|
| + std::find(compression_queue_.begin(),
|
| + compression_queue_.end(),
|
| + thumbnail) == compression_queue_.end());
|
| +}
|
| +
|
| +void ThumbnailCache::CleanupThumbnail(scoped_refptr<Thumbnail> thumbnail) {
|
| + CleanupThumbnail(thumbnail, NULL);
|
| +}
|
| +
|
| +void ThumbnailCache::CleanupThumbnail(
|
| + scoped_refptr<Thumbnail> thumbnail,
|
| + const ExpiringThumbnailCache* forced_source) {
|
| + if (!thumbnail)
|
| + return;
|
| +
|
| + if (ShouldCleanupThumbnail(thumbnail, forced_source)) {
|
| + thumbnail->CleanupThumbnail();
|
| + thumbnail->CleanupCPUData(false);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::NotifyListenersOfThumbnailChange(TabId tab_id,
|
| + bool upgrade) {
|
| + NotifyListenersOfUIResourceUpdate(tab_id);
|
| +
|
| + for (ThumbnailChangeListenerSet::iterator iter =
|
| + thumbnail_change_listeners_.begin();
|
| + iter != thumbnail_change_listeners_.end();
|
| + iter++) {
|
| + (*iter)->OnThumbnailChanged(tab_id, upgrade);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::NotifyListenersOfUIResourceUpdate(TabId tab_id) {
|
| + scoped_refptr<Thumbnail> thumbnail = GetThumbnail(tab_id);
|
| + for (ThumbnailChangeListenerSet::iterator iter =
|
| + thumbnail_change_listeners_.begin();
|
| + iter != thumbnail_change_listeners_.end();
|
| + iter++) {
|
| + if (thumbnail) {
|
| + (*iter)->OnUIResourceUpdated(
|
| + tab_id,
|
| + thumbnail->ui_resource_id(),
|
| + thumbnail->GetContentSize(),
|
| + gfx::ScaleSize(thumbnail->data_size(), 1.f / thumbnail->scale()));
|
| + } else {
|
| + (*iter)->OnUIResourceUpdated(tab_id, 0, gfx::Size(), gfx::SizeF());
|
| + }
|
| + }
|
| +}
|
| +
|
| +base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) const {
|
| + return disk_cache_path_.Append(base::IntToString(tab_id));
|
| +}
|
| +
|
| +bool ThumbnailCache::ThumbnailInputFileExists(TabId tab_id) const {
|
| + return base::PathExists(GetFilePath(tab_id));
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveCorruptThumbnailSource(TabId tab_id) {
|
| + RemoveFromDisk(tab_id);
|
| +}
|
| +
|
| +void ThumbnailCache::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
|
| + if (cache_.Get(tab_id) ||
|
| + std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
|
| + visible_ids_.end() ||
|
| + cache_.size() < cache_.MaximumCacheSize()) {
|
| + return;
|
| + }
|
| +
|
| + RemoveThumbnailFromCache();
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveThumbnailFromCache() {
|
| + 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 (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
|
| + visible_ids_.end()) {
|
| + 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) {
|
| + CleanupThumbnail(cache_.Remove(key_to_remove));
|
| + NotifyListenersOfThumbnailChange(key_to_remove, false);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::AddIdToVisibleIds(TabId tab_id) {
|
| + if (std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
|
| + visible_ids_.end())
|
| + return;
|
| +
|
| + if (visible_ids_.size() > cache_.MaximumCacheSize() - 1)
|
| + visible_ids_.pop_back();
|
| +
|
| + visible_ids_.push_front(tab_id);
|
| +}
|
| +
|
| +ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() {
|
| +}
|
| +
|
| +ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData(
|
| + const base::Time& current_time,
|
| + const GURL& url)
|
| + : capture_time_(current_time), url_(url) {
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromDiskAtAndAboveId(TabId min_id) {
|
| + base::Closure remove_task =
|
| + base::Bind(&ThumbnailCache::RemoveFromDiskAtAndAboveIdTask,
|
| + disk_cache_path_,
|
| + min_id);
|
| + content::BrowserThread::PostTask(
|
| + content::BrowserThread::FILE, FROM_HERE, remove_task);
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromDisk(TabId tab_id) {
|
| + base::Closure remove_task =
|
| + base::Bind(&ThumbnailCache::RemoveFromDiskTask, GetFilePath(tab_id));
|
| + content::BrowserThread::PostTask(
|
| + content::BrowserThread::FILE, FROM_HERE, remove_task);
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromDiskTask(const base::FilePath& file_path) {
|
| + base::DeleteFile(file_path, false);
|
| +}
|
| +
|
| +void ThumbnailCache::RemoveFromDiskAtAndAboveIdTask(
|
| + const base::FilePath& dir_path,
|
| + TabId min_id) {
|
| + base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
|
| + while (true) {
|
| + base::FilePath path = enumerator.Next();
|
| + if (path.empty())
|
| + break;
|
| + base::FileEnumerator::FileInfo info = enumerator.GetInfo();
|
| + TabId tab_id;
|
| + bool success = base::StringToInt(info.GetName().value(), &tab_id);
|
| + if (success && tab_id >= min_id)
|
| + base::DeleteFile(path, false);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::ThumbnailRequested(TabId tab_id) {
|
| + scoped_refptr<Thumbnail> thumbnail = GetThumbnail(tab_id);
|
| + if (thumbnail) {
|
| + if (!thumbnail->RebuildThumbnail())
|
| + return;
|
| + cc::UIResourceId ui_resource_id = thumbnail->ui_resource_id();
|
| + if (!ui_resource_id)
|
| + return;
|
| + NotifyListenersOfUIResourceUpdate(tab_id);
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::OnUIResourcesAreInvalid() {
|
| + RemoveThumbnailsAndScheduleReload();
|
| +}
|
| +
|
| +void ThumbnailCache::OnRecreateUIResources() {
|
| + RemoveThumbnailsAndScheduleReload();
|
| +}
|
| +
|
| +base::Time ThumbnailCache::CurrentTime() {
|
| + return base::Time::Now();
|
| +}
|
| +
|
| +ThumbnailCache::Thumbnail::Thumbnail(
|
| + TabId tab_id,
|
| + content::UIResourceProvider* ui_resource_provider)
|
| + : tab_id_(tab_id),
|
| + ui_resource_provider_(ui_resource_provider),
|
| + ui_resource_id_(0),
|
| + format_(DECOMPRESSED),
|
| + rebuild_pending_(false),
|
| + data_write_pending_(false),
|
| + from_stream_(false) {
|
| + DCHECK(ui_resource_provider);
|
| +}
|
| +
|
| +ThumbnailCache::Thumbnail::Thumbnail(
|
| + TabId tab_id,
|
| + SkBitmap bitmap,
|
| + float scale,
|
| + content::UIResourceProvider* ui_resource_provider)
|
| + : tab_id_(tab_id),
|
| + raw_data_(bitmap),
|
| + scale_(scale),
|
| + ui_resource_provider_(ui_resource_provider),
|
| + ui_resource_id_(0),
|
| + format_(DECOMPRESSED),
|
| + rebuild_pending_(false),
|
| + data_write_pending_(false),
|
| + from_stream_(false) {
|
| + DCHECK(ui_resource_provider);
|
| + SetRawThumbnailData(bitmap);
|
| +}
|
| +
|
| +ThumbnailCache::Thumbnail::Thumbnail(const Thumbnail& thumbnail)
|
| + : tab_id_(thumbnail.tab_id_),
|
| + raw_data_(thumbnail.raw_data_),
|
| + scale_(thumbnail.scale_),
|
| + ui_resource_provider_(thumbnail.ui_resource_provider_),
|
| + ui_resource_id_(thumbnail.ui_resource_id_),
|
| + compressed_data_(thumbnail.compressed_data_),
|
| + data_size_(thumbnail.data_size_),
|
| + scaled_content_size_(thumbnail.scaled_content_size_),
|
| + format_(thumbnail.format_),
|
| + rebuild_pending_(thumbnail.rebuild_pending_),
|
| + data_write_pending_(thumbnail.data_write_pending_),
|
| + from_stream_(thumbnail.from_stream_) {
|
| +}
|
| +
|
| +ThumbnailCache::Thumbnail::~Thumbnail() {
|
| +}
|
| +
|
| +ThumbnailCache::ApproxThumbnail::ApproxThumbnail(
|
| + TabId tab_id,
|
| + SkBitmap bitmap,
|
| + float scale,
|
| + content::UIResourceProvider* ui_resource_provider)
|
| + : Thumbnail(tab_id, bitmap, scale, ui_resource_provider) {
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::CleanupThumbnail() {
|
| + if (ui_resource_id_) {
|
| + ui_resource_provider_->DeleteUIResource(ui_resource_id_);
|
| + ui_resource_id_ = 0;
|
| + }
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::AttemptToScheduleRebuildFromData() {
|
| + if (!raw_data_.empty() ||
|
| + (compressed_data_ && !compressed_data_->info().isEmpty())) {
|
| + rebuild_pending_ = true;
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::CleanupCPUData(bool force) {
|
| + if (!CleanupRequired() && !force)
|
| + return;
|
| + raw_data_ = SkBitmap();
|
| + compressed_data_.clear();
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::RebuildThumbnail() {
|
| + if (!rebuild_pending_)
|
| + return false;
|
| + CleanupThumbnail();
|
| + if ((raw_data_.empty() && format_ == DECOMPRESSED) ||
|
| + (!compressed_data_ && format_ == COMPRESSED))
|
| + return false;
|
| + bool is_bitmap_transient = true;
|
| + if (format_ == COMPRESSED) {
|
| + ui_resource_id_ = ui_resource_provider_->GenerateCompressedUIResource(
|
| + compressed_data_, is_bitmap_transient);
|
| + } else {
|
| + ui_resource_id_ = ui_resource_provider_->GenerateUIResource(
|
| + raw_data_, is_bitmap_transient);
|
| + }
|
| +
|
| + rebuild_pending_ = false;
|
| + CleanupCPUData(false);
|
| + return true;
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::CleanupRequired() {
|
| + return !(CompressionRequired() || DataWriteRequired() || rebuild_pending_ ||
|
| + (raw_data_.empty() && !compressed_data_));
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::CompressionRequired() const {
|
| + return kEnableCompression && !compressed_data_ && !raw_data_.empty();
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::DataWriteRequired() const {
|
| + return data_write_pending_ && !from_stream_;
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::HasCompressedRawData() const {
|
| + return compressed_data_;
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::IsValid() const {
|
| + return ui_resource_provider_ && scale_ > 0 && !data_size_.IsEmpty() &&
|
| + !scaled_content_size_.IsEmpty() &&
|
| + (ui_resource_id_ || !raw_data_.empty() || compressed_data_);
|
| +}
|
| +
|
| +gfx::Size ThumbnailCache::Thumbnail::GetContentSize() const {
|
| + return gfx::ToRoundedSize(gfx::ScaleSize(scaled_content_size_, 1.f / scale_));
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::Compress() {
|
| + SkBitmap raw_data = raw_data_;
|
| + if (CompressionRequired() && format_ == DECOMPRESSED && !raw_data.empty()) {
|
| + SkAutoLockPixels raw_data_lock(raw_data);
|
| + gfx::Size raw_data_size(raw_data.width(), raw_data.height());
|
| + DCHECK_EQ(raw_data.config(), SkBitmap::kARGB_8888_Config);
|
| + size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
|
| + size_t stride = pixel_size * raw_data_size.width();
|
| +
|
| + gfx::Size encoded_size = GetEncodedSize(raw_data_size);
|
| + size_t encoded_bytes =
|
| + etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
|
| + SkImageInfo info = {encoded_size.width(),
|
| + encoded_size.height(),
|
| + kUnknown_SkColorType,
|
| + kUnpremul_SkAlphaType};
|
| + skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef(
|
| + SkData::NewFromMalloc(new uint8_t[encoded_bytes], encoded_bytes));
|
| + skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef(
|
| + SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
|
| +
|
| + etc1_pixel_ref->lockPixels();
|
| + 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->unlockPixels();
|
| + if (success) {
|
| + etc1_pixel_ref->setImmutable();
|
| + SetCompressedThumbnailData(etc1_pixel_ref, raw_data_size);
|
| + } else {
|
| + Invalidate();
|
| + }
|
| + }
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::WriteThumbnailToFile(base::File& file) {
|
| + // We assume caller has already initialized the file and will also close it.
|
| + DCHECK(file.IsValid());
|
| +
|
| + if (DataWriteRequired() && format_ == COMPRESSED && compressed_data_) {
|
| + if (kEnableCompression) {
|
| + compressed_data_->lockPixels();
|
| + bool success = true;
|
| + int scaled_content_width = scaled_content_size_.width();
|
| + int scaled_content_height = scaled_content_size_.height();
|
| + int data_width = data_size_.width();
|
| + int data_height = data_size_.height();
|
| +
|
| + if (file.WriteAtCurrentPos(reinterpret_cast<const char*>(&kCompressedKey),
|
| + sizeof(int)) < 0 ||
|
| + file.WriteAtCurrentPos(
|
| + reinterpret_cast<const char*>(&scaled_content_width),
|
| + sizeof(int)) < 0 ||
|
| + file.WriteAtCurrentPos(
|
| + reinterpret_cast<const char*>(&scaled_content_height),
|
| + sizeof(int)) < 0 ||
|
| + file.WriteAtCurrentPos(reinterpret_cast<const char*>(&data_width),
|
| + sizeof(int)) < 0 ||
|
| + file.WriteAtCurrentPos(reinterpret_cast<const char*>(&data_height),
|
| + sizeof(int)) < 0 ||
|
| + file.WriteAtCurrentPos(reinterpret_cast<const char*>(&scale_),
|
| + sizeof(float)) < 0) {
|
| + success = false;
|
| + }
|
| +
|
| + size_t compressed_bytes =
|
| + etc1_get_encoded_data_size(data_size_.width(), data_size_.height());
|
| +
|
| + if (file.WriteAtCurrentPos(
|
| + reinterpret_cast<char*>(compressed_data_->pixels()),
|
| + compressed_bytes) < 0)
|
| + success = false;
|
| +
|
| + compressed_data_->unlockPixels();
|
| +
|
| + if (!success)
|
| + Invalidate();
|
| +
|
| + return success;
|
| + } else {
|
| + // In the original Java implementation, we store a jpg version when the
|
| + // |kEnableCompression| flag is off. Keep as NOTIMPLEMENTED for now.
|
| + NOTIMPLEMENTED();
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool ThumbnailCache::Thumbnail::ReadThumbnailFromFile(base::File& file) {
|
| + // We assume caller has already initialized the file and will also close it.
|
| + DCHECK(file.IsValid());
|
| +
|
| + if (kEnableCompression) {
|
| + int key;
|
| + bool success = true;
|
| + if (file.ReadAtCurrentPos(reinterpret_cast<char*>(&key), sizeof(int)) < 0 ||
|
| + key != kCompressedKey)
|
| + success = false;
|
| +
|
| + int width, height;
|
| + if (file.ReadAtCurrentPos(reinterpret_cast<char*>(&width), sizeof(int)) <
|
| + 0 ||
|
| + file.ReadAtCurrentPos(reinterpret_cast<char*>(&height), sizeof(int)) <
|
| + 0)
|
| + success = false;
|
| + scaled_content_size_ = gfx::Size(width, height);
|
| +
|
| + if (file.ReadAtCurrentPos(reinterpret_cast<char*>(&width), sizeof(int)) <
|
| + 0 ||
|
| + file.ReadAtCurrentPos(reinterpret_cast<char*>(&height), sizeof(int)) <
|
| + 0)
|
| + success = false;
|
| + data_size_ = gfx::Size(width, height);
|
| +
|
| + if (file.ReadAtCurrentPos(reinterpret_cast<char*>(&scale_), sizeof(float)) <
|
| + 0)
|
| + success = false;
|
| +
|
| + size_t compressed_bytes =
|
| + etc1_get_encoded_data_size(data_size_.width(), data_size_.height());
|
| +
|
| + SkImageInfo info = {data_size_.width(),
|
| + data_size_.height(),
|
| + kUnknown_SkColorType,
|
| + kUnpremul_SkAlphaType};
|
| +
|
| + scoped_ptr<uint8_t[]> data(new uint8_t[compressed_bytes]);
|
| + if (file.ReadAtCurrentPos(reinterpret_cast<char*>(data.get()),
|
| + compressed_bytes) < 0)
|
| + success = false;
|
| +
|
| + skia::RefPtr<SkData> etc1_pixel_data =
|
| + skia::AdoptRef(SkData::NewFromMalloc(data.release(), compressed_bytes));
|
| + compressed_data_ = skia::AdoptRef(
|
| + SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
|
| +
|
| + format_ = COMPRESSED;
|
| + from_stream_ = true;
|
| + rebuild_pending_ = true;
|
| + data_write_pending_ = false;
|
| +
|
| + if (!success)
|
| + Invalidate();
|
| +
|
| + return success;
|
| + } else {
|
| + NOTIMPLEMENTED();
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::Invalidate() {
|
| + CleanupCPUData(true);
|
| + ui_resource_id_ = 0;
|
| + data_size_ = gfx::Size();
|
| + scaled_content_size_ = gfx::Size();
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::SetCompressedThumbnailData(
|
| + skia::RefPtr<SkPixelRef> compressed_data,
|
| + gfx::Size scaled_content_size) {
|
| + compressed_data_ = compressed_data;
|
| + raw_data_ = SkBitmap();
|
| + if (compressed_data) {
|
| + data_size_ = gfx::Size(compressed_data_->info().width(),
|
| + compressed_data_->info().height());
|
| + scaled_content_size_ = scaled_content_size;
|
| + format_ = COMPRESSED;
|
| + rebuild_pending_ = true;
|
| + data_write_pending_ = true;
|
| + } else {
|
| + data_size_ = gfx::Size();
|
| + scaled_content_size_ = gfx::Size();
|
| + rebuild_pending_ = false;
|
| + data_write_pending_ = false;
|
| + }
|
| +}
|
| +
|
| +void ThumbnailCache::Thumbnail::SetRawThumbnailData(SkBitmap bitmap) {
|
| + raw_data_ = bitmap;
|
| + compressed_data_.clear();
|
| + data_size_ = gfx::Size(bitmap.width(), bitmap.height());
|
| + scaled_content_size_ = data_size_;
|
| + format_ = DECOMPRESSED;
|
| + rebuild_pending_ = true;
|
| + data_write_pending_ = !kEnableCompression;
|
| +}
|
| +
|
| +ThumbnailCache::ThumbnailBitmap::ThumbnailBitmap() : scale_(0.f) {
|
| +}
|
| +
|
| +ThumbnailCache::ThumbnailBitmap::ThumbnailBitmap(SkBitmap bitmap, float scale)
|
| + : bitmap_(bitmap), scale_(scale) {
|
| +}
|
| +
|
| +bool ThumbnailCache::ThumbnailBitmap::IsValid() const {
|
| + return !bitmap_.empty() && scale_ > 0;
|
| +}
|
| +
|
| +ThumbnailCache::ThumbnailBitmap
|
| +ThumbnailCache::ThumbnailBitmap::CreateApproximation() const {
|
| + SkAutoLockPixels bitmap_lock(bitmap_);
|
| + float new_scale = 1.f / kApproximationScaleFactor;
|
| +
|
| + gfx::Size dst_size = gfx::ToFlooredSize(
|
| + gfx::ScaleSize(gfx::Size(bitmap_.width(), bitmap_.height()), new_scale));
|
| + SkBitmap dst_bitmap;
|
| + dst_bitmap.setConfig(
|
| + bitmap_.getConfig(), dst_size.width(), dst_size.height());
|
| + dst_bitmap.allocPixels();
|
| + dst_bitmap.eraseColor(0);
|
| + SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
|
| +
|
| + SkCanvas canvas(dst_bitmap);
|
| + canvas.scale(new_scale, new_scale);
|
| + canvas.drawBitmap(bitmap_, 0, 0, NULL);
|
| + dst_bitmap.setImmutable();
|
| +
|
| + return ThumbnailBitmap(dst_bitmap, new_scale * scale_);
|
| +}
|
| +
|
| +ThumbnailCache::TabReadbackRequest::TabReadbackRequest(
|
| + TabId tab_id,
|
| + content::ContentViewCore* content_view_core,
|
| + float thumbnail_scale,
|
| + base::Callback<void(int, const ThumbnailBitmap&)> end_callback)
|
| + : tab_id_(tab_id),
|
| + content_view_core_(content_view_core),
|
| + thumbnail_scale_(thumbnail_scale),
|
| + end_callback_(end_callback),
|
| + drop_after_readback_(false),
|
| + weak_factory_(this) {
|
| +}
|
| +
|
| +void ThumbnailCache::TabReadbackRequest::Run() {
|
| + base::Callback<void(bool, const SkBitmap&)> result_callback =
|
| + base::Bind(&TabReadbackRequest::OnFinishGetTabThumbnailBitmap,
|
| + weak_factory_.GetWeakPtr());
|
| + if (content_view_core_) {
|
| + DCHECK(content_view_core_->GetWebContents());
|
| + content_view_core_->GetWebContents()
|
| + ->GetRenderViewHost()
|
| + ->LockBackingStore();
|
| +
|
| + // Calling this method with an empty rect will return a bitmap of the size
|
| + // of the content.
|
| + content_view_core_->GetScaledContentBitmap(thumbnail_scale_,
|
| + SkBitmap::kARGB_8888_Config,
|
| + gfx::Rect(),
|
| + result_callback);
|
| + } else {
|
| + result_callback.Run(false, SkBitmap());
|
| + }
|
| +}
|
| +
|
| +// This callback should be called on the UI thread. So it should be safe to
|
| +// call the thumbnail cache.
|
| +void ThumbnailCache::TabReadbackRequest::OnFinishGetTabThumbnailBitmap(
|
| + bool success,
|
| + const SkBitmap& bitmap) {
|
| + if (content_view_core_) {
|
| + DCHECK(content_view_core_->GetWebContents());
|
| + content_view_core_->GetWebContents()
|
| + ->GetRenderViewHost()
|
| + ->UnlockBackingStore();
|
| + }
|
| + if (success) {
|
| + SkBitmap local_bitmap = bitmap;
|
| + local_bitmap.setImmutable();
|
| + end_callback_.Run(tab_id_, ThumbnailBitmap(local_bitmap, thumbnail_scale_));
|
| + } else {
|
| + end_callback_.Run(tab_id_, ThumbnailBitmap());
|
| + }
|
| +}
|
|
|