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()); |
+ } |
+} |