1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/android/thumbnail/thumbnail_store.h"
6
7 #include <algorithm>
8 #include <cmath>
9
10 #include "base/big_endian.h"
11 #include "base/files/file.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/threading/worker_pool.h"
17 #include "base/time/time.h"
18 #include "content/public/browser/android/ui_resource_provider.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "third_party/android_opengl/etc1/etc1.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "third_party/skia/include/core/SkCanvas.h"
23 #include "third_party/skia/include/core/SkData.h"
24 #include "third_party/skia/include/core/SkMallocPixelRef.h"
25 #include "third_party/skia/include/core/SkPixelRef.h"
26 #include "ui/gfx/android/device_display_info.h"
27 #include "ui/gfx/geometry/size_conversions.h"
28
29 namespace {
30
31 const float kApproximationScaleFactor = 4.f;
32 const base::TimeDelta kCaptureMinRequestTimeMs(
33 base::TimeDelta::FromMilliseconds(1000));
34
35 const int kCompressedKey = 0xABABABAB;
36 const int kCurrentExtraVersion = 1;
37
38 // Indicates whether we prefer to have more free CPU memory over GPU memory.
39 const bool kPreferCPUMemory = true;
40
NextPowerOfTwo(size_t x)41 size_t NextPowerOfTwo(size_t x) {
42 --x;
43 x |= x >> 1;
44 x |= x >> 2;
45 x |= x >> 4;
46 x |= x >> 8;
47 x |= x >> 16;
48 return x + 1;
49 }
50
RoundUpMod4(size_t x)51 size_t RoundUpMod4(size_t x) {
52 return (x + 3) & ~3;
53 }
54
GetEncodedSize(const gfx::Size & bitmap_size,bool supports_npot)55 gfx::Size GetEncodedSize(const gfx::Size& bitmap_size, bool supports_npot) {
56 DCHECK(!bitmap_size.IsEmpty());
57 if (!supports_npot)
58 return gfx::Size(NextPowerOfTwo(bitmap_size.width()),
59 NextPowerOfTwo(bitmap_size.height()));
60 else
61 return gfx::Size(RoundUpMod4(bitmap_size.width()),
62 RoundUpMod4(bitmap_size.height()));
63 }
64
65 template<typename T>
ReadBigEndianFromFile(base::File & file,T * out)66 bool ReadBigEndianFromFile(base::File& file, T* out) {
67 char buffer[sizeof(T)];
68 if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
69 return false;
70 base::ReadBigEndian(buffer, out);
71 return true;
72 }
73
74 template<typename T>
WriteBigEndianToFile(base::File & file,T val)75 bool WriteBigEndianToFile(base::File& file, T val) {
76 char buffer[sizeof(T)];
77 base::WriteBigEndian(buffer, val);
78 return file.WriteAtCurrentPos(buffer, sizeof(T)) == sizeof(T);
79 }
80
ReadBigEndianFloatFromFile(base::File & file,float * out)81 bool ReadBigEndianFloatFromFile(base::File& file, float* out) {
82 char buffer[sizeof(float)];
83 if (file.ReadAtCurrentPos(buffer, sizeof(buffer)) != sizeof(buffer))
84 return false;
85
86 #if defined(ARCH_CPU_LITTLE_ENDIAN)
87 for (size_t i = 0; i < sizeof(float) / 2; i++) {
88 char tmp = buffer[i];
89 buffer[i] = buffer[sizeof(float) - 1 - i];
90 buffer[sizeof(float) - 1 - i] = tmp;
91 }
92 #endif
93 memcpy(out, buffer, sizeof(buffer));
94
95 return true;
96 }
97
WriteBigEndianFloatToFile(base::File & file,float val)98 bool WriteBigEndianFloatToFile(base::File& file, float val) {
99 char buffer[sizeof(float)];
100 memcpy(buffer, &val, sizeof(buffer));
101
102 #if defined(ARCH_CPU_LITTLE_ENDIAN)
103 for (size_t i = 0; i < sizeof(float) / 2; i++) {
104 char tmp = buffer[i];
105 buffer[i] = buffer[sizeof(float) - 1 - i];
106 buffer[sizeof(float) - 1 - i] = tmp;
107 }
108 #endif
109 return file.WriteAtCurrentPos(buffer, sizeof(buffer)) == sizeof(buffer);
110 }
111
112 } // anonymous namespace
113
ThumbnailStore(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)114 ThumbnailStore::ThumbnailStore(const std::string& disk_cache_path_str,
115 size_t default_cache_size,
116 size_t approximation_cache_size,
117 size_t compression_queue_max_size,
118 size_t write_queue_max_size,
119 bool use_approximation_thumbnail)
120 : disk_cache_path_(disk_cache_path_str),
121 compression_queue_max_size_(compression_queue_max_size),
122 write_queue_max_size_(write_queue_max_size),
123 use_approximation_thumbnail_(use_approximation_thumbnail),
124 compression_tasks_count_(0),
125 write_tasks_count_(0),
126 read_in_progress_(false),
127 cache_(default_cache_size),
128 approximation_cache_(approximation_cache_size),
129 ui_resource_provider_(NULL),
130 weak_factory_(this) {
131 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
132 }
133
~ThumbnailStore()134 ThumbnailStore::~ThumbnailStore() {
135 SetUIResourceProvider(NULL);
136 }
137
SetUIResourceProvider(content::UIResourceProvider * ui_resource_provider)138 void ThumbnailStore::SetUIResourceProvider(
139 content::UIResourceProvider* ui_resource_provider) {
140 if (ui_resource_provider_ == ui_resource_provider)
141 return;
142
143 approximation_cache_.Clear();
144 cache_.Clear();
145
146 ui_resource_provider_ = ui_resource_provider;
147 }
148
AddThumbnailStoreObserver(ThumbnailStoreObserver * observer)149 void ThumbnailStore::AddThumbnailStoreObserver(
150 ThumbnailStoreObserver* observer) {
151 if (!observers_.HasObserver(observer))
152 observers_.AddObserver(observer);
153 }
154
RemoveThumbnailStoreObserver(ThumbnailStoreObserver * observer)155 void ThumbnailStore::RemoveThumbnailStoreObserver(
156 ThumbnailStoreObserver* observer) {
157 if (observers_.HasObserver(observer))
158 observers_.RemoveObserver(observer);
159 }
160
Put(TabId tab_id,const SkBitmap & bitmap,float thumbnail_scale)161 void ThumbnailStore::Put(TabId tab_id,
162 const SkBitmap& bitmap,
163 float thumbnail_scale) {
164 if (!ui_resource_provider_ || bitmap.empty() || thumbnail_scale <= 0)
165 return;
166
167 DCHECK(thumbnail_meta_data_.find(tab_id) != thumbnail_meta_data_.end());
168
169 base::Time time_stamp = thumbnail_meta_data_[tab_id].capture_time();
170 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
171 tab_id, time_stamp, thumbnail_scale, ui_resource_provider_, this);
172 thumbnail->SetBitmap(bitmap);
173
174 RemoveFromReadQueue(tab_id);
175 MakeSpaceForNewItemIfNecessary(tab_id);
176 cache_.Put(tab_id, thumbnail.Pass());
177
178 if (use_approximation_thumbnail_) {
179 std::pair<SkBitmap, float> approximation =
180 CreateApproximation(bitmap, thumbnail_scale);
181 scoped_ptr<Thumbnail> approx_thumbnail = Thumbnail::Create(
182 tab_id, time_stamp, approximation.second, ui_resource_provider_, this);
183 approx_thumbnail->SetBitmap(approximation.first);
184 approximation_cache_.Put(tab_id, approx_thumbnail.Pass());
185 }
186 CompressThumbnailIfNecessary(tab_id, time_stamp, bitmap, thumbnail_scale);
187 }
188
Remove(TabId tab_id)189 void ThumbnailStore::Remove(TabId tab_id) {
190 cache_.Remove(tab_id);
191 approximation_cache_.Remove(tab_id);
192 thumbnail_meta_data_.erase(tab_id);
193 RemoveFromDisk(tab_id);
194 RemoveFromReadQueue(tab_id);
195 }
196
Get(TabId tab_id,bool force_disk_read,bool allow_approximation)197 Thumbnail* ThumbnailStore::Get(TabId tab_id,
198 bool force_disk_read,
199 bool allow_approximation) {
200 Thumbnail* thumbnail = cache_.Get(tab_id);
201 if (thumbnail) {
202 thumbnail->CreateUIResource();
203 return thumbnail;
204 }
205
206 if (force_disk_read &&
207 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) !=
208 visible_ids_.end() &&
209 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
210 read_queue_.end()) {
211 read_queue_.push_back(tab_id);
212 ReadNextThumbnail();
213 }
214
215 if (allow_approximation) {
216 thumbnail = approximation_cache_.Get(tab_id);
217 if (thumbnail) {
218 thumbnail->CreateUIResource();
219 return thumbnail;
220 }
221 }
222
223 return NULL;
224 }
225
RemoveFromDiskAtAndAboveId(TabId min_id)226 void ThumbnailStore::RemoveFromDiskAtAndAboveId(TabId min_id) {
227 base::Closure remove_task =
228 base::Bind(&ThumbnailStore::RemoveFromDiskAtAndAboveIdTask,
229 disk_cache_path_,
230 min_id);
231 content::BrowserThread::PostTask(
232 content::BrowserThread::FILE, FROM_HERE, remove_task);
233 }
234
InvalidateThumbnailIfChanged(TabId tab_id,const GURL & url)235 void ThumbnailStore::InvalidateThumbnailIfChanged(TabId tab_id,
236 const GURL& url) {
237 ThumbnailMetaDataMap::iterator meta_data_iter =
238 thumbnail_meta_data_.find(tab_id);
239 if (meta_data_iter == thumbnail_meta_data_.end()) {
240 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(base::Time(), url);
241 } else if (meta_data_iter->second.url() != url) {
242 Remove(tab_id);
243 }
244 }
245
CheckAndUpdateThumbnailMetaData(TabId tab_id,const GURL & url)246 bool ThumbnailStore::CheckAndUpdateThumbnailMetaData(TabId tab_id,
247 const GURL& url) {
248 base::Time current_time = base::Time::Now();
249 ThumbnailMetaDataMap::iterator meta_data_iter =
250 thumbnail_meta_data_.find(tab_id);
251 if (meta_data_iter != thumbnail_meta_data_.end() &&
252 meta_data_iter->second.url() == url &&
253 (current_time - meta_data_iter->second.capture_time()) <
254 kCaptureMinRequestTimeMs) {
255 return false;
256 }
257
258 thumbnail_meta_data_[tab_id] = ThumbnailMetaData(current_time, url);
259 return true;
260 }
261
UpdateVisibleIds(const TabIdList & priority)262 void ThumbnailStore::UpdateVisibleIds(const TabIdList& priority) {
263 if (priority.empty()) {
264 visible_ids_.clear();
265 return;
266 }
267
268 size_t ids_size = std::min(priority.size(), cache_.MaximumCacheSize());
269 if (visible_ids_.size() == ids_size) {
270 // Early out if called with the same input as last time (We only care
271 // about the first mCache.MaximumCacheSize() entries).
272 bool lists_differ = false;
273 TabIdList::const_iterator visible_iter = visible_ids_.begin();
274 TabIdList::const_iterator priority_iter = priority.begin();
275 while (visible_iter != visible_ids_.end() &&
276 priority_iter != priority.end()) {
277 if (*priority_iter != *visible_iter) {
278 lists_differ = true;
279 break;
280 }
281 visible_iter++;
282 priority_iter++;
283 }
284
285 if (!lists_differ)
286 return;
287 }
288
289 read_queue_.clear();
290 visible_ids_.clear();
291 size_t count = 0;
292 TabIdList::const_iterator iter = priority.begin();
293 while (iter != priority.end() && count < ids_size) {
294 TabId tab_id = *iter;
295 visible_ids_.push_back(tab_id);
296 if (!cache_.Get(tab_id) &&
297 std::find(read_queue_.begin(), read_queue_.end(), tab_id) ==
298 read_queue_.end()) {
299 read_queue_.push_back(tab_id);
300 }
301 iter++;
302 count++;
303 }
304
305 ReadNextThumbnail();
306 }
307
DecompressThumbnailFromFile(TabId tab_id,const base::Callback<void (bool,SkBitmap)> & post_decompress_callback)308 void ThumbnailStore::DecompressThumbnailFromFile(
309 TabId tab_id,
310 const base::Callback<void(bool, SkBitmap)>&
311 post_decompress_callback) {
312 base::FilePath file_path = GetFilePath(tab_id);
313
314 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
315 decompress_task = base::Bind(
316 &ThumbnailStore::DecompressionTask, post_decompress_callback);
317
318 content::BrowserThread::PostTask(
319 content::BrowserThread::FILE,
320 FROM_HERE,
321 base::Bind(&ThumbnailStore::ReadTask, true, file_path, decompress_task));
322 }
323
RemoveFromDisk(TabId tab_id)324 void ThumbnailStore::RemoveFromDisk(TabId tab_id) {
325 base::FilePath file_path = GetFilePath(tab_id);
326 base::Closure task =
327 base::Bind(&ThumbnailStore::RemoveFromDiskTask, file_path);
328 content::BrowserThread::PostTask(
329 content::BrowserThread::FILE, FROM_HERE, task);
330 }
331
RemoveFromDiskTask(const base::FilePath & file_path)332 void ThumbnailStore::RemoveFromDiskTask(const base::FilePath& file_path) {
333 if (base::PathExists(file_path))
334 base::DeleteFile(file_path, false);
335 }
336
RemoveFromDiskAtAndAboveIdTask(const base::FilePath & dir_path,TabId min_id)337 void ThumbnailStore::RemoveFromDiskAtAndAboveIdTask(
338 const base::FilePath& dir_path,
339 TabId min_id) {
340 base::FileEnumerator enumerator(dir_path, false, base::FileEnumerator::FILES);
341 while (true) {
342 base::FilePath path = enumerator.Next();
343 if (path.empty())
344 break;
345 base::FileEnumerator::FileInfo info = enumerator.GetInfo();
346 TabId tab_id;
347 bool success = base::StringToInt(info.GetName().value(), &tab_id);
348 if (success && tab_id >= min_id)
349 base::DeleteFile(path, false);
350 }
351 }
352
WriteThumbnailIfNecessary(TabId tab_id,skia::RefPtr<SkPixelRef> compressed_data,float scale,const gfx::Size & content_size)353 void ThumbnailStore::WriteThumbnailIfNecessary(
354 TabId tab_id,
355 skia::RefPtr<SkPixelRef> compressed_data,
356 float scale,
357 const gfx::Size& content_size) {
358 if (write_tasks_count_ >= write_queue_max_size_)
359 return;
360
361 write_tasks_count_++;
362
363 base::Callback<void()> post_write_task =
364 base::Bind(&ThumbnailStore::PostWriteTask, weak_factory_.GetWeakPtr());
365 content::BrowserThread::PostTask(content::BrowserThread::FILE,
366 FROM_HERE,
367 base::Bind(&ThumbnailStore::WriteTask,
368 GetFilePath(tab_id),
369 compressed_data,
370 scale,
371 content_size,
372 post_write_task));
373 }
374
CompressThumbnailIfNecessary(TabId tab_id,const base::Time & time_stamp,const SkBitmap & bitmap,float scale)375 void ThumbnailStore::CompressThumbnailIfNecessary(
376 TabId tab_id,
377 const base::Time& time_stamp,
378 const SkBitmap& bitmap,
379 float scale) {
380 if (compression_tasks_count_ >= compression_queue_max_size_) {
381 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
382 return;
383 }
384
385 compression_tasks_count_++;
386
387 base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>
388 post_compression_task = base::Bind(&ThumbnailStore::PostCompressionTask,
389 weak_factory_.GetWeakPtr(),
390 tab_id,
391 time_stamp,
392 scale);
393
394 gfx::Size raw_data_size(bitmap.width(), bitmap.height());
395 gfx::Size encoded_size = GetEncodedSize(
396 raw_data_size, ui_resource_provider_->SupportsETC1NonPowerOfTwo());
397
398 base::WorkerPool::PostTask(FROM_HERE,
399 base::Bind(&ThumbnailStore::CompressionTask,
400 bitmap,
401 encoded_size,
402 post_compression_task),
403 true);
404 }
405
ReadNextThumbnail()406 void ThumbnailStore::ReadNextThumbnail() {
407 if (read_queue_.empty() || read_in_progress_)
408 return;
409
410 TabId tab_id = read_queue_.front();
411 read_in_progress_ = true;
412
413 base::FilePath file_path = GetFilePath(tab_id);
414
415 base::Callback<void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>
416 post_read_task = base::Bind(
417 &ThumbnailStore::PostReadTask, weak_factory_.GetWeakPtr(), tab_id);
418
419 content::BrowserThread::PostTask(
420 content::BrowserThread::FILE,
421 FROM_HERE,
422 base::Bind(&ThumbnailStore::ReadTask, false, file_path, post_read_task));
423 }
424
MakeSpaceForNewItemIfNecessary(TabId tab_id)425 void ThumbnailStore::MakeSpaceForNewItemIfNecessary(TabId tab_id) {
426 if (cache_.Get(tab_id) ||
427 std::find(visible_ids_.begin(), visible_ids_.end(), tab_id) ==
428 visible_ids_.end() ||
429 cache_.size() < cache_.MaximumCacheSize()) {
430 return;
431 }
432
433 TabId key_to_remove;
434 bool found_key_to_remove = false;
435
436 // 1. Find a cached item not in this list
437 for (ExpiringThumbnailCache::iterator iter = cache_.begin();
438 iter != cache_.end();
439 iter++) {
440 if (std::find(visible_ids_.begin(), visible_ids_.end(), iter->first) ==
441 visible_ids_.end()) {
442 key_to_remove = iter->first;
443 found_key_to_remove = true;
444 break;
445 }
446 }
447
448 if (!found_key_to_remove) {
449 // 2. Find the least important id we can remove.
450 for (TabIdList::reverse_iterator riter = visible_ids_.rbegin();
451 riter != visible_ids_.rend();
452 riter++) {
453 if (cache_.Get(*riter)) {
454 key_to_remove = *riter;
455 break;
456 found_key_to_remove = true;
457 }
458 }
459 }
460
461 if (found_key_to_remove)
462 cache_.Remove(key_to_remove);
463 }
464
RemoveFromReadQueue(TabId tab_id)465 void ThumbnailStore::RemoveFromReadQueue(TabId tab_id) {
466 TabIdList::iterator read_iter =
467 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
468 if (read_iter != read_queue_.end())
469 read_queue_.erase(read_iter);
470 }
471
InvalidateCachedThumbnail(Thumbnail * thumbnail)472 void ThumbnailStore::InvalidateCachedThumbnail(Thumbnail* thumbnail) {
473 DCHECK(thumbnail);
474 TabId tab_id = thumbnail->tab_id();
475 cc::UIResourceId uid = thumbnail->ui_resource_id();
476
477 Thumbnail* cached_thumbnail = cache_.Get(tab_id);
478 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
479 cache_.Remove(tab_id);
480
481 cached_thumbnail = approximation_cache_.Get(tab_id);
482 if (cached_thumbnail && cached_thumbnail->ui_resource_id() == uid)
483 approximation_cache_.Remove(tab_id);
484 }
485
GetFilePath(TabId tab_id) const486 base::FilePath ThumbnailStore::GetFilePath(TabId tab_id) const {
487 return disk_cache_path_.Append(base::IntToString(tab_id));
488 }
489
490 namespace {
491
WriteToFile(base::File & file,const gfx::Size & content_size,const float scale,skia::RefPtr<SkPixelRef> compressed_data)492 bool WriteToFile(base::File& file,
493 const gfx::Size& content_size,
494 const float scale,
495 skia::RefPtr<SkPixelRef> compressed_data) {
496 if (!file.IsValid())
497 return false;
498
499 if (!WriteBigEndianToFile(file, kCompressedKey))
500 return false;
501
502 if (!WriteBigEndianToFile(file, content_size.width()))
503 return false;
504
505 if (!WriteBigEndianToFile(file, content_size.height()))
506 return false;
507
508 // Write ETC1 header.
509 compressed_data->lockPixels();
510
511 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
512 etc1_pkm_format_header(etc1_buffer,
513 compressed_data->info().width(),
514 compressed_data->info().height());
515
516 int header_bytes_written = file.WriteAtCurrentPos(
517 reinterpret_cast<char*>(etc1_buffer), ETC_PKM_HEADER_SIZE);
518 if (header_bytes_written != ETC_PKM_HEADER_SIZE)
519 return false;
520
521 int data_size = etc1_get_encoded_data_size(
522 compressed_data->info().width(),
523 compressed_data->info().height());
524 int pixel_bytes_written = file.WriteAtCurrentPos(
525 reinterpret_cast<char*>(compressed_data->pixels()),
526 data_size);
527 if (pixel_bytes_written != data_size)
528 return false;
529
530 compressed_data->unlockPixels();
531
532 if (!WriteBigEndianToFile(file, kCurrentExtraVersion))
533 return false;
534
535 if (!WriteBigEndianFloatToFile(file, 1.f / scale))
536 return false;
537
538 return true;
539 }
540
541 } // anonymous namespace
542
WriteTask(const base::FilePath & file_path,skia::RefPtr<SkPixelRef> compressed_data,float scale,const gfx::Size & content_size,const base::Callback<void ()> & post_write_task)543 void ThumbnailStore::WriteTask(const base::FilePath& file_path,
544 skia::RefPtr<SkPixelRef> compressed_data,
545 float scale,
546 const gfx::Size& content_size,
547 const base::Callback<void()>& post_write_task) {
548 DCHECK(compressed_data);
549
550 base::File file(file_path,
551 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
552
553 bool success = WriteToFile(file,
554 content_size,
555 scale,
556 compressed_data);
557
558 file.Close();
559
560 if (!success)
561 base::DeleteFile(file_path, false);
562
563 content::BrowserThread::PostTask(
564 content::BrowserThread::UI, FROM_HERE, post_write_task);
565 }
566
PostWriteTask()567 void ThumbnailStore::PostWriteTask() {
568 write_tasks_count_--;
569 }
570
CompressionTask(SkBitmap raw_data,gfx::Size encoded_size,const base::Callback<void (skia::RefPtr<SkPixelRef>,const gfx::Size &)> & post_compression_task)571 void ThumbnailStore::CompressionTask(
572 SkBitmap raw_data,
573 gfx::Size encoded_size,
574 const base::Callback<void(skia::RefPtr<SkPixelRef>, const gfx::Size&)>&
575 post_compression_task) {
576 skia::RefPtr<SkPixelRef> compressed_data;
577 gfx::Size content_size;
578
579 if (!raw_data.empty()) {
580 SkAutoLockPixels raw_data_lock(raw_data);
581 gfx::Size raw_data_size(raw_data.width(), raw_data.height());
582 size_t pixel_size = 4; // Pixel size is 4 bytes for kARGB_8888_Config.
583 size_t stride = pixel_size * raw_data_size.width();
584
585 size_t encoded_bytes =
586 etc1_get_encoded_data_size(encoded_size.width(), encoded_size.height());
587 SkImageInfo info = SkImageInfo::Make(encoded_size.width(),
588 encoded_size.height(),
589 kUnknown_SkColorType,
590 kUnpremul_SkAlphaType);
591 skia::RefPtr<SkData> etc1_pixel_data = skia::AdoptRef(
592 SkData::NewUninitialized(encoded_bytes));
593 skia::RefPtr<SkMallocPixelRef> etc1_pixel_ref = skia::AdoptRef(
594 SkMallocPixelRef::NewWithData(info, 0, NULL, etc1_pixel_data.get()));
595
596 etc1_pixel_ref->lockPixels();
597 bool success = etc1_encode_image(
598 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
599 raw_data_size.width(),
600 raw_data_size.height(),
601 pixel_size,
602 stride,
603 reinterpret_cast<unsigned char*>(etc1_pixel_ref->pixels()),
604 encoded_size.width(),
605 encoded_size.height());
606 etc1_pixel_ref->setImmutable();
607 etc1_pixel_ref->unlockPixels();
608
609 if (success) {
610 compressed_data = etc1_pixel_ref;
611 content_size = raw_data_size;
612 }
613 }
614
615 content::BrowserThread::PostTask(
616 content::BrowserThread::UI,
617 FROM_HERE,
618 base::Bind(post_compression_task, compressed_data, content_size));
619 }
620
PostCompressionTask(TabId tab_id,const base::Time & time_stamp,float scale,skia::RefPtr<SkPixelRef> compressed_data,const gfx::Size & content_size)621 void ThumbnailStore::PostCompressionTask(
622 TabId tab_id,
623 const base::Time& time_stamp,
624 float scale,
625 skia::RefPtr<SkPixelRef> compressed_data,
626 const gfx::Size& content_size) {
627 compression_tasks_count_--;
628 if (!compressed_data) {
629 RemoveOnMatchedTimeStamp(tab_id, time_stamp);
630 return;
631 }
632
633 Thumbnail* thumbnail = cache_.Get(tab_id);
634 if (thumbnail) {
635 if (thumbnail->time_stamp() != time_stamp)
636 return;
637 thumbnail->SetCompressedBitmap(compressed_data, content_size);
638 thumbnail->CreateUIResource();
639 }
640 WriteThumbnailIfNecessary(tab_id, compressed_data, scale, content_size);
641 }
642
643 namespace {
644
ReadFromFile(base::File & file,gfx::Size * out_content_size,float * out_scale,skia::RefPtr<SkPixelRef> * out_pixels)645 bool ReadFromFile(base::File& file,
646 gfx::Size* out_content_size,
647 float* out_scale,
648 skia::RefPtr<SkPixelRef>* out_pixels) {
649 if (!file.IsValid())
650 return false;
651
652 int key = 0;
653 if (!ReadBigEndianFromFile(file, &key))
654 return false;
655
656 if (key != kCompressedKey)
657 return false;
658
659 int content_width = 0;
660 if (!ReadBigEndianFromFile(file, &content_width) || content_width <= 0)
661 return false;
662
663 int content_height = 0;
664 if (!ReadBigEndianFromFile(file, &content_height) || content_height <= 0)
665 return false;
666
667 out_content_size->SetSize(content_width, content_height);
668
669 // Read ETC1 header.
670 int header_bytes_read = 0;
671 unsigned char etc1_buffer[ETC_PKM_HEADER_SIZE];
672 header_bytes_read = file.ReadAtCurrentPos(
673 reinterpret_cast<char*>(etc1_buffer),
674 ETC_PKM_HEADER_SIZE);
675 if (header_bytes_read != ETC_PKM_HEADER_SIZE)
676 return false;
677
678 if (!etc1_pkm_is_valid(etc1_buffer))
679 return false;
680
681 int raw_width = 0;
682 raw_width = etc1_pkm_get_width(etc1_buffer);
683 if (raw_width <= 0)
684 return false;
685
686 int raw_height = 0;
687 raw_height = etc1_pkm_get_height(etc1_buffer);
688 if (raw_height <= 0)
689 return false;
690
691 // Do some simple sanity check validation. We can't have thumbnails larger
692 // than the max display size of the screen. We also can't have etc1 texture
693 // data larger than the next power of 2 up from that.
694 gfx::DeviceDisplayInfo display_info;
695 int max_dimension = std::max(display_info.GetDisplayWidth(),
696 display_info.GetDisplayHeight());
697
698 if (content_width > max_dimension
699 || content_height > max_dimension
700 || static_cast<size_t>(raw_width) > NextPowerOfTwo(max_dimension)
701 || static_cast<size_t>(raw_height) > NextPowerOfTwo(max_dimension)) {
702 return false;
703 }
704
705 int data_size = etc1_get_encoded_data_size(raw_width, raw_height);
706 skia::RefPtr<SkData> etc1_pixel_data =
707 skia::AdoptRef(SkData::NewUninitialized(data_size));
708
709 int pixel_bytes_read = file.ReadAtCurrentPos(
710 reinterpret_cast<char*>(etc1_pixel_data->writable_data()),
711 data_size);
712
713 if (pixel_bytes_read != data_size)
714 return false;
715
716 SkImageInfo info = SkImageInfo::Make(raw_width,
717 raw_height,
718 kUnknown_SkColorType,
719 kUnpremul_SkAlphaType);
720
721 *out_pixels = skia::AdoptRef(
722 SkMallocPixelRef::NewWithData(info,
723 0,
724 NULL,
725 etc1_pixel_data.get()));
726
727 int extra_data_version = 0;
728 if (!ReadBigEndianFromFile(file, &extra_data_version))
729 return false;
730
731 *out_scale = 1.f;
732 if (extra_data_version == 1) {
733 if (!ReadBigEndianFloatFromFile(file, out_scale))
734 return false;
735
736 if (*out_scale == 0.f)
737 return false;
738
739 *out_scale = 1.f / *out_scale;
740 }
741
742 return true;
743 }
744
745 }// anonymous namespace
746
ReadTask(bool decompress,const base::FilePath & file_path,const base::Callback<void (skia::RefPtr<SkPixelRef>,float,const gfx::Size &)> & post_read_task)747 void ThumbnailStore::ReadTask(
748 bool decompress,
749 const base::FilePath& file_path,
750 const base::Callback<
751 void(skia::RefPtr<SkPixelRef>, float, const gfx::Size&)>&
752 post_read_task) {
753 gfx::Size content_size;
754 float scale = 0.f;
755 skia::RefPtr<SkPixelRef> compressed_data;
756
757 if (base::PathExists(file_path)) {
758 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
759
760
761 bool valid_contents = ReadFromFile(file,
762 &content_size,
763 &scale,
764 &compressed_data);
765 file.Close();
766
767 if (!valid_contents) {
768 content_size.SetSize(0, 0);
769 scale = 0.f;
770 compressed_data.clear();
771 base::DeleteFile(file_path, false);
772 }
773 }
774
775 if (decompress) {
776 base::WorkerPool::PostTask(
777 FROM_HERE,
778 base::Bind(post_read_task, compressed_data, scale, content_size),
779 true);
780 } else {
781 content::BrowserThread::PostTask(
782 content::BrowserThread::UI,
783 FROM_HERE,
784 base::Bind(post_read_task, compressed_data, scale, content_size));
785 }
786 }
787
PostReadTask(TabId tab_id,skia::RefPtr<SkPixelRef> compressed_data,float scale,const gfx::Size & content_size)788 void ThumbnailStore::PostReadTask(TabId tab_id,
789 skia::RefPtr<SkPixelRef> compressed_data,
790 float scale,
791 const gfx::Size& content_size) {
792 read_in_progress_ = false;
793
794 TabIdList::iterator iter =
795 std::find(read_queue_.begin(), read_queue_.end(), tab_id);
796 if (iter == read_queue_.end()) {
797 ReadNextThumbnail();
798 return;
799 }
800
801 read_queue_.erase(iter);
802
803 if (!cache_.Get(tab_id) && compressed_data) {
804 ThumbnailMetaDataMap::iterator meta_iter =
805 thumbnail_meta_data_.find(tab_id);
806 base::Time time_stamp = base::Time::Now();
807 if (meta_iter != thumbnail_meta_data_.end())
808 time_stamp = meta_iter->second.capture_time();
809
810 MakeSpaceForNewItemIfNecessary(tab_id);
811 scoped_ptr<Thumbnail> thumbnail = Thumbnail::Create(
812 tab_id, time_stamp, scale, ui_resource_provider_, this);
813 thumbnail->SetCompressedBitmap(compressed_data,
814 content_size);
815 if (kPreferCPUMemory)
816 thumbnail->CreateUIResource();
817
818 cache_.Put(tab_id, thumbnail.Pass());
819 NotifyObserversOfThumbnailRead(tab_id);
820 }
821
822 ReadNextThumbnail();
823 }
824
NotifyObserversOfThumbnailRead(TabId tab_id)825 void ThumbnailStore::NotifyObserversOfThumbnailRead(TabId tab_id) {
826 FOR_EACH_OBSERVER(
827 ThumbnailStoreObserver, observers_, OnFinishedThumbnailRead(tab_id));
828 }
829
RemoveOnMatchedTimeStamp(TabId tab_id,const base::Time & time_stamp)830 void ThumbnailStore::RemoveOnMatchedTimeStamp(TabId tab_id,
831 const base::Time& time_stamp) {
832 // We remove the cached version if it matches the tab_id and the time_stamp.
833 Thumbnail* thumbnail = cache_.Get(tab_id);
834 Thumbnail* approx_thumbnail = approximation_cache_.Get(tab_id);
835 if ((thumbnail && thumbnail->time_stamp() == time_stamp) ||
836 (approx_thumbnail && approx_thumbnail->time_stamp() == time_stamp)) {
837 Remove(tab_id);
838 }
839 return;
840 }
841
DecompressionTask(const base::Callback<void (bool,SkBitmap)> & post_decompression_callback,skia::RefPtr<SkPixelRef> compressed_data,float scale,const gfx::Size & content_size)842 void ThumbnailStore::DecompressionTask(
843 const base::Callback<void(bool, SkBitmap)>&
844 post_decompression_callback,
845 skia::RefPtr<SkPixelRef> compressed_data,
846 float scale,
847 const gfx::Size& content_size) {
848 SkBitmap raw_data_small;
849 bool success = false;
850
851 if (compressed_data.get()) {
852 gfx::Size buffer_size = gfx::Size(compressed_data->info().width(),
853 compressed_data->info().height());
854
855 SkBitmap raw_data;
856 raw_data.allocPixels(SkImageInfo::Make(buffer_size.width(),
857 buffer_size.height(),
858 kRGBA_8888_SkColorType,
859 kOpaque_SkAlphaType));
860 SkAutoLockPixels raw_data_lock(raw_data);
861 compressed_data->lockPixels();
862 success = etc1_decode_image(
863 reinterpret_cast<unsigned char*>(compressed_data->pixels()),
864 reinterpret_cast<unsigned char*>(raw_data.getPixels()),
865 buffer_size.width(),
866 buffer_size.height(),
867 raw_data.bytesPerPixel(),
868 raw_data.rowBytes());
869 compressed_data->unlockPixels();
870 raw_data.setImmutable();
871
872 if (!success) {
873 // Leave raw_data_small empty for consistency with other failure modes.
874 } else if (content_size == buffer_size) {
875 // Shallow copy the pixel reference.
876 raw_data_small = raw_data;
877 } else {
878 // The content size is smaller than the buffer size (likely because of
879 // a power-of-two rounding), so deep copy the bitmap.
880 raw_data_small.allocPixels(SkImageInfo::Make(content_size.width(),
881 content_size.height(),
882 kRGBA_8888_SkColorType,
883 kOpaque_SkAlphaType));
884 SkAutoLockPixels raw_data_small_lock(raw_data_small);
885 SkCanvas small_canvas(raw_data_small);
886 small_canvas.drawBitmap(raw_data, 0, 0);
887 raw_data_small.setImmutable();
888 }
889 }
890
891 content::BrowserThread::PostTask(
892 content::BrowserThread::UI,
893 FROM_HERE,
894 base::Bind(post_decompression_callback, success, raw_data_small));
895 }
896
ThumbnailMetaData()897 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData() {
898 }
899
ThumbnailMetaData(const base::Time & current_time,const GURL & url)900 ThumbnailStore::ThumbnailMetaData::ThumbnailMetaData(
901 const base::Time& current_time,
902 const GURL& url)
903 : capture_time_(current_time), url_(url) {
904 }
905
CreateApproximation(const SkBitmap & bitmap,float scale)906 std::pair<SkBitmap, float> ThumbnailStore::CreateApproximation(
907 const SkBitmap& bitmap,
908 float scale) {
909 DCHECK(!bitmap.empty());
910 DCHECK_GT(scale, 0);
911 SkAutoLockPixels bitmap_lock(bitmap);
912 float new_scale = 1.f / kApproximationScaleFactor;
913
914 gfx::Size dst_size = gfx::ToFlooredSize(
915 gfx::ScaleSize(gfx::Size(bitmap.width(), bitmap.height()), new_scale));
916 SkBitmap dst_bitmap;
917 dst_bitmap.allocPixels(SkImageInfo::Make(dst_size.width(),
918 dst_size.height(),
919 bitmap.info().fColorType,
920 bitmap.info().fAlphaType));
921 dst_bitmap.eraseColor(0);
922 SkAutoLockPixels dst_bitmap_lock(dst_bitmap);
923
924 SkCanvas canvas(dst_bitmap);
925 canvas.scale(new_scale, new_scale);
926 canvas.drawBitmap(bitmap, 0, 0, NULL);
927 dst_bitmap.setImmutable();
928
929 return std::make_pair(dst_bitmap, new_scale * scale);
930 }
931