Index: chrome/browser/android/thumbnail/thumbnail_cache.cc |
diff --git a/chrome/browser/android/thumbnail/thumbnail_cache.cc b/chrome/browser/android/thumbnail/thumbnail_cache.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..435e85c8ef04ef7f579ed696bdf02c49f1a098ec |
--- /dev/null |
+++ b/chrome/browser/android/thumbnail/thumbnail_cache.cc |
@@ -0,0 +1,520 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/android/thumbnail/thumbnail_cache.h" |
+ |
+#include <algorithm> |
+#include <cmath> |
+ |
+#include "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 "content/public/browser/android/ui_resource_provider.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "third_party/skia/include/core/SkBitmap.h" |
+#include "third_party/skia/include/core/SkCanvas.h" |
+#include "ui/gfx/geometry/size_conversions.h" |
+ |
+namespace { |
+ |
+const bool kDropCachedNTPOnLowMemory = true; |
+const float kApproximationScaleFactor = 4.f; |
+const base::TimeDelta kCaptureMinRequestTimeMs( |
+ base::TimeDelta::FromMilliseconds(1000)); |
+ |
+} // 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_thumbnail) |
+ : disk_cache_path_(disk_cache_path_str), |
+ compression_queue_max_size_(compression_queue_max_size), |
+ write_queue_max_size_(write_queue_max_size), |
+ use_approximation_thumbnail_(use_approximation_thumbnail), |
+ cache_(default_cache_size), |
+ approximation_cache_(approximation_cache_size), |
+ ui_resource_provider_(NULL), |
+ 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; |
+ |
+ approximation_cache_.Clear(); |
+ cache_.Clear(); |
+ |
+ ui_resource_provider_ = ui_resource_provider; |
+} |
+ |
+void ThumbnailCache::AddThumbnailCacheObserver( |
+ ThumbnailCacheObserver* observer) { |
+ if (!observers_.HasObserver(observer)) |
+ observers_.AddObserver(observer); |
+} |
+ |
+void ThumbnailCache::RemoveThumbnailCacheObserver( |
+ ThumbnailCacheObserver* observer) { |
+ if (observers_.HasObserver(observer)) |
+ observers_.RemoveObserver(observer); |
+} |
+ |
+void ThumbnailCache::Put(TabId tab_id, |
+ const SkBitmap& bitmap, |
+ float thumbnail_scale) { |
+ if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0) |
+ return; |
+ |
+ scoped_refptr<ThumbnailImpl> thumbnail = make_scoped_refptr(new ThumbnailImpl( |
+ tab_id, bitmap, thumbnail_scale, this, ui_resource_provider_)); |
+ |
+ RemoveFromQueues(tab_id); |
+ MakeSpaceForNewItemIfNecessary(tab_id); |
+ cache_.Put(tab_id, thumbnail); |
+ |
+ if (use_approximation_thumbnail_) { |
+ std::pair<SkBitmap, float> approximation = |
+ CreateApproximation(bitmap, thumbnail_scale); |
+ scoped_refptr<ThumbnailImpl> approx_thumbnail = |
+ make_scoped_refptr(new ThumbnailImpl(tab_id, |
+ approximation.first, |
+ approximation.second, |
+ this, |
+ ui_resource_provider_)); |
+ approximation_cache_.Put(tab_id, approx_thumbnail); |
+ } |
+ |
+ CompressThumbnailIfNecessary(thumbnail); |
+} |
+ |
+void ThumbnailCache::Remove(TabId tab_id) { |
+ cache_.Remove(tab_id); |
+ approximation_cache_.Remove(tab_id); |
+ thumbnail_meta_data_.erase(tab_id); |
+ RemoveFileFromDisk(GetFilePath(tab_id)); |
+ RemoveFromQueues(tab_id); |
+} |
+ |
+scoped_refptr<Thumbnail> ThumbnailCache::Get(TabId tab_id) { |
+ scoped_refptr<ThumbnailImpl> thumbnail = cache_.Get(tab_id); |
+ if (!thumbnail) |
+ thumbnail = approximation_cache_.Get(tab_id); |
+ |
+ // If we are trying to find a thumbnail that is not in memory cache and yet is |
+ // visible, then try paging it in from disk. |
+ if (!thumbnail && |
+ std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) != |
+ visible_ids_.end() && |
+ std::find(read_queue_.begin(), read_queue_.end(), tab_id) == |
+ read_queue_.end()) { |
+ read_queue_.push_back(tab_id); |
+ ReadNextThumbnail(); |
+ } |
+ return thumbnail; |
+} |
+ |
+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::InvalidateThumbnailIfChanged(TabId tab_id, |
+ const GURL& url) { |
+ ThumbnailMetaDataMap::iterator meta_data_iter = |
+ thumbnail_meta_data_.find(tab_id); |
+ if (meta_data_iter == thumbnail_meta_data_.end()) { |
+ thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url); |
+ } else if (meta_data_iter->second.url() != url) { |
+ Remove(tab_id); |
+ } |
+} |
+ |
+bool ThumbnailCache::CheckAndUpdateThumbnailMetaData(TabId tab_id, |
+ const GURL& url) { |
+ ThumbnailMetaDataMap::iterator meta_data_iter = |
+ thumbnail_meta_data_.find(tab_id); |
+ if (meta_data_iter != thumbnail_meta_data_.end() && |
+ meta_data_iter->second.url() == url && |
+ (CurrentTime() - meta_data_iter->second.capture_time()) < |
+ kCaptureMinRequestTimeMs) { |
+ return false; |
+ } |
+ |
+ thumbnail_meta_data_[tab_id] = ThumbnailMetaData(CurrentTime(), url); |
+ return true; |
+} |
+ |
+void ThumbnailCache::UpdateVisibleIds(const TabIdList& priority) { |
+ if (priority.empty()) { |
+ visible_ids_.clear(); |
+ return; |
+ } |
+ |
+ size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize()); |
+ if (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 = visible_ids_.begin(), |
David Trainor- moved to gerrit
2014/06/17 08:12:11
A while loop might look a bit cleaner.
powei
2014/06/19 23:05:59
Done.
|
+ priority_iter = priority.begin(); |
+ visible_iter != visible_ids_.end() && priority_iter != priority.end(); |
+ visible_iter++, priority_iter++) { |
+ if (*priority_iter != *visible_iter) { |
+ lists_differ = true; |
+ break; |
+ } |
+ } |
+ if (!lists_differ) |
+ return; |
+ } |
+ |
+ read_queue_.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; |
+ visible_ids_.push_back(tab_id); |
+ if (!cache_.Get(tab_id) && |
+ std::find(read_queue_.begin(), read_queue_.end(), tab_id) == |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Is this to check if the same id is in the list twi
powei
2014/06/19 23:05:59
Yes it is. We probably don't need this if we're s
|
+ read_queue_.end()) { |
+ read_queue_.push_back(tab_id); |
+ } |
+ } |
+ |
+ ReadNextThumbnail(); |
+} |
+ |
+void ThumbnailCache::RemoveFileFromDisk(const base::FilePath& file_path) { |
+ base::Closure task = |
+ base::Bind(&ThumbnailCache::RemoveFileFromDiskTask, file_path); |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::FILE, FROM_HERE, task); |
+} |
+ |
+void ThumbnailCache::RemoveFileFromDiskTask(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::WriteThumbnailIfNecessary( |
+ scoped_refptr<ThumbnailImpl> thumbnail) { |
+ RemoveDuplicateIdsFromQueueHelper(write_queue_, thumbnail); |
+ if (write_queue_.size() > write_queue_max_size_) |
+ write_queue_.pop_front(); |
+ |
+ write_queue_.push_back(thumbnail); |
+ WriteNextThumbnail(); |
+} |
+ |
+void ThumbnailCache::CompressThumbnailIfNecessary( |
+ scoped_refptr<ThumbnailImpl> thumbnail) { |
+ RemoveDuplicateIdsFromQueueHelper(compression_queue_, thumbnail); |
+ if (compression_queue_.size() > compression_queue_max_size_) |
+ compression_queue_.pop_front(); |
+ |
+ compression_queue_.push_back(thumbnail); |
+ CompressNextThumbnail(); |
+} |
+ |
+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; |
+ } |
+ |
+ 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) |
+ cache_.Remove(key_to_remove); |
+} |
+ |
+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<ThumbnailImpl> write_thumbnail = *iter; |
+ if (write_thumbnail->tab_id() != tab_id) |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Why not if (==) iter.remove()? Same below and for
powei
2014/06/19 23:05:59
Done. changed to "iter = write_queue_.erase(iter);
|
+ 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<ThumbnailImpl> compress_thumbnail = *iter; |
+ if (compress_thumbnail->tab_id() != tab_id) |
+ new_compression_queue.push_back(compress_thumbnail); |
+ } |
+ compression_queue_ = new_compression_queue; |
+} |
+ |
+bool ThumbnailCache::RemoveDuplicateIdsFromQueueHelper( |
+ ThumbnailQueue& queue, |
+ scoped_refptr<ThumbnailImpl> thumbnail) { |
+ bool found = false; |
+ ThumbnailQueue::iterator iter = queue.begin(); |
+ ThumbnailQueue good_queue; |
+ for (ThumbnailQueue::iterator iter = queue.begin(); iter != queue.end(); |
+ iter++) { |
+ if ((*iter)->tab_id() != thumbnail->tab_id()) { |
+ good_queue.push_back(thumbnail); |
+ } else { |
+ found = true; |
+ } |
+ } |
+ queue = good_queue; |
+ return found; |
+} |
+ |
+void ThumbnailCache::InvalidateCachedThumbnail( |
+ scoped_refptr<ThumbnailImpl> thumbnail) { |
+ TabId tab_id = thumbnail->tab_id(); |
+ if (cache_.Get(tab_id) == thumbnail) |
+ cache_.Remove(tab_id); |
+ |
+ if (approximation_cache_.Get(tab_id) == thumbnail) |
+ approximation_cache_.Remove(tab_id); |
+} |
+ |
+base::FilePath ThumbnailCache::GetFilePath(TabId tab_id) const { |
+ return disk_cache_path_.Append(base::IntToString(tab_id)); |
+} |
+ |
+base::Time ThumbnailCache::CurrentTime() { |
+ return base::Time::Now(); |
+} |
+ |
+void ThumbnailCache::WriteNextThumbnail() { |
David Trainor- moved to gerrit
2014/06/17 08:12:11
What if we're already waiting for a write to finis
powei
2014/06/19 23:05:59
Are you thinking about how the size of the write_q
David Trainor- moved to gerrit
2014/06/25 07:40:15
I'm more worried that we have a write queue that d
powei
2014/07/07 17:24:51
Agreed offline that we'd keep a count instead of a
|
+ if (write_queue_.empty()) |
+ return; |
+ |
+ scoped_refptr<ThumbnailImpl> thumbnail = write_queue_.front(); |
+ write_queue_.pop_front(); |
+ |
+ if (!thumbnail->IsValid()) |
+ return; |
+ |
+ scoped_refptr<ThumbnailImpl> in_thumbnail = |
+ thumbnail->PassDataToNewThumbnail(); |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Won't this steal the reference from the object in
powei
2014/06/19 23:05:58
The point is to steal the reference. This way we'
David Trainor- moved to gerrit
2014/06/25 07:40:16
But since we're building the ui resource id on dem
powei
2014/07/07 17:24:51
Done. Moved to more explicit passing of ref-counte
|
+ |
+ base::Closure write_task = base::Bind(&ThumbnailCache::WriteTask, |
+ in_thumbnail, |
+ GetFilePath(thumbnail->tab_id())); |
+ base::Closure post_write_task = base::Bind( |
+ &ThumbnailCache::WriteNextThumbnail, weak_factory_.GetWeakPtr()); |
+ |
+ content::BrowserThread::PostTaskAndReply( |
+ content::BrowserThread::FILE, FROM_HERE, write_task, post_write_task); |
+} |
+ |
+void ThumbnailCache::WriteTask(scoped_refptr<ThumbnailImpl> thumbnail, |
+ const base::FilePath& file_path) { |
+ DCHECK(thumbnail); |
+ DCHECK(thumbnail->IsValid()); |
+ |
+ base::File file(file_path, |
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
+ bool success = ThumbnailImpl::WriteToFile(thumbnail, file); |
+ file.Close(); |
+ |
+ if (!success) |
+ RemoveFileFromDisk(file_path); |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Is posting this safe? What if the app dies before
powei
2014/06/19 23:05:59
Done. Good catch. called DeleteFile directly ins
|
+} |
+ |
+void ThumbnailCache::ReadNextThumbnail() { |
+ if (read_queue_.empty()) |
+ return; |
+ |
+ TabId tab_id = read_queue_.front(); |
+ |
+ // Create an empty thumbnail to hold the content of the read. |
+ scoped_refptr<ThumbnailImpl> output_thumbnail = make_scoped_refptr( |
+ new ThumbnailImpl(tab_id, SkBitmap(), 0.f, this, ui_resource_provider_)); |
+ |
+ base::FilePath file_path = GetFilePath(tab_id); |
+ base::Closure read_task = |
+ base::Bind(&ThumbnailCache::ReadTask, file_path, output_thumbnail); |
+ base::Closure post_read_task = base::Bind(&ThumbnailCache::PostReadTask, |
+ weak_factory_.GetWeakPtr(), |
+ output_thumbnail); |
+ content::BrowserThread::PostTaskAndReply( |
+ content::BrowserThread::FILE, FROM_HERE, read_task, post_read_task); |
+} |
+ |
+void ThumbnailCache::ReadTask(const base::FilePath& file_path, |
+ scoped_refptr<ThumbnailImpl> 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()); |
+ bool success = ThumbnailImpl::ReadFromFile(file, thumbnail); |
+ file.Close(); |
+ |
+ if (!success) |
+ RemoveFileFromDisk(file_path); |
+} |
+ |
+void ThumbnailCache::PostReadTask(scoped_refptr<ThumbnailImpl> 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); |
+ TabId tab_id = thumbnail->tab_id(); |
+ MakeSpaceForNewItemIfNecessary(tab_id); |
+ cache_.Put(tab_id, thumbnail); |
+ NotifyObserversOfThumbnailRead(tab_id); |
+ ReadNextThumbnail(); |
+} |
+ |
+void ThumbnailCache::CompressNextThumbnail() { |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Shouldn't this queue up the tasks and only run one
powei
2014/06/19 23:05:59
see comment for write task.
|
+ if (!compression_queue_.empty()) { |
+ scoped_refptr<ThumbnailImpl> thumbnail = compression_queue_.front(); |
+ compression_queue_.pop_front(); |
+ |
+ scoped_refptr<ThumbnailImpl> in_thumbnail = |
+ thumbnail->PassDataToNewThumbnail(); |
David Trainor- moved to gerrit
2014/06/17 08:12:11
Doesn't this also steal the bitmap/data from the t
powei
2014/06/19 23:05:59
This does steal the reference, and once compressio
David Trainor- moved to gerrit
2014/06/25 07:40:15
Still not sure how this works if we compress befor
|
+ |
+ scoped_refptr<ThumbnailImpl> out_thumbnail = |
+ scoped_refptr<ThumbnailImpl>(new ThumbnailImpl(in_thumbnail->tab_id(), |
+ SkBitmap(), |
+ 0.f, |
+ this, |
+ ui_resource_provider_)); |
+ base::Closure compression_task = |
+ base::Bind(&ThumbnailImpl::Compress, in_thumbnail, out_thumbnail); |
+ base::Closure post_compression_task = |
+ base::Bind(&ThumbnailCache::PostCompressionTask, |
+ weak_factory_.GetWeakPtr(), |
+ out_thumbnail); |
+ |
+ DCHECK(compression_thread_.message_loop_proxy()); |
+ compression_thread_.message_loop_proxy()->PostTaskAndReply( |
+ FROM_HERE, compression_task, post_compression_task); |
+ } |
+} |
+ |
+void ThumbnailCache::PostCompressionTask( |
+ scoped_refptr<ThumbnailImpl> compressed_thumbnail) { |
+ DCHECK(compressed_thumbnail); |
+ TabId tab_id = compressed_thumbnail->tab_id(); |
+ cache_.Remove(tab_id); |
+ if (compressed_thumbnail->IsValid()) { |
+ cache_.Put(tab_id, compressed_thumbnail); |
+ WriteThumbnailIfNecessary(compressed_thumbnail); |
+ } |
+ CompressNextThumbnail(); |
+} |
+ |
+void ThumbnailCache::NotifyObserversOfThumbnailRead(TabId tab_id) { |
+ FOR_EACH_OBSERVER( |
+ ThumbnailCacheObserver, observers_, OnFinishedThumbnailRead(tab_id)); |
+} |
+ |
+ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData() { |
+} |
+ |
+ThumbnailCache::ThumbnailMetaData::ThumbnailMetaData( |
+ const base::Time& current_time, |
+ const GURL& url) |
+ : capture_time_(current_time), url_(url) { |
+} |
+ |
+std::pair<SkBitmap, float> ThumbnailCache::CreateApproximation( |
+ const SkBitmap& bitmap, |
+ float scale) { |
+ DCHECK(!bitmap.empty()); |
+ DCHECK_GT(scale, 0); |
+ 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 std::make_pair(dst_bitmap, new_scale * scale); |
+} |