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/compositor/tab_content_manager.h"
6
7 #include <android/bitmap.h>
8 #include <stddef.h>
9
10 #include <algorithm>
11 #include <memory>
12 #include <utility>
13
14 #include "base/android/callback_android.h"
15 #include "base/android/jni_android.h"
16 #include "base/android/jni_string.h"
17 #include "base/android/scoped_java_ref.h"
18 #include "base/bind.h"
19 #include "base/callback_helpers.h"
20 #include "base/macros.h"
21 #include "base/metrics/field_trial_params.h"
22 #include "cc/layers/layer.h"
23 #include "chrome/android/chrome_jni_headers/TabContentManager_jni.h"
24 #include "chrome/browser/android/compositor/layer/thumbnail_layer.h"
25 #include "chrome/browser/android/tab_android.h"
26 #include "chrome/browser/flags/android/chrome_feature_list.h"
27 #include "chrome/browser/thumbnail/cc/thumbnail.h"
28 #include "content/public/browser/render_frame_host.h"
29 #include "content/public/browser/render_view_host.h"
30 #include "content/public/browser/render_widget_host.h"
31 #include "content/public/browser/render_widget_host_view.h"
32 #include "content/public/browser/web_contents.h"
33 #include "skia/ext/image_operations.h"
34 #include "ui/android/resources/ui_resource_provider.h"
35 #include "ui/android/view_android.h"
36 #include "ui/gfx/android/java_bitmap.h"
37 #include "ui/gfx/geometry/dip_util.h"
38 #include "ui/gfx/geometry/rect.h"
39 #include "url/gurl.h"
40
41 using base::android::JavaParamRef;
42 using base::android::JavaRef;
43
44 namespace {
45
46 using TabReadbackCallback = base::OnceCallback<void(float, const SkBitmap&)>;
47
48 } // namespace
49
50 namespace android {
51
52 class TabContentManager::TabReadbackRequest {
53 public:
TabReadbackRequest(content::RenderWidgetHostView * rwhv,float thumbnail_scale,bool crop_to_match_aspect_ratio,TabReadbackCallback end_callback)54 TabReadbackRequest(content::RenderWidgetHostView* rwhv,
55 float thumbnail_scale,
56 bool crop_to_match_aspect_ratio,
57 TabReadbackCallback end_callback)
58 : thumbnail_scale_(thumbnail_scale),
59 end_callback_(std::move(end_callback)),
60 drop_after_readback_(false) {
61 DCHECK(rwhv);
62 auto result_callback =
63 base::BindOnce(&TabReadbackRequest::OnFinishGetTabThumbnailBitmap,
64 weak_factory_.GetWeakPtr());
65
66 gfx::Size view_size_in_pixels =
67 rwhv->GetNativeView()->GetPhysicalBackingSize();
68 if (view_size_in_pixels.IsEmpty()) {
69 std::move(result_callback).Run(SkBitmap());
70 return;
71 }
72 if (crop_to_match_aspect_ratio) {
73 double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
74 chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio",
75 1.0);
76 aspect_ratio = ThumbnailCache::clampAspectRatio(aspect_ratio, 0.5, 2.0);
77 int height = std::min(view_size_in_pixels.height(),
78 (int)(view_size_in_pixels.width() / aspect_ratio));
79 view_size_in_pixels.set_height(height);
80 }
81 gfx::Rect source_rect = gfx::Rect(view_size_in_pixels);
82 gfx::Size thumbnail_size(
83 gfx::ScaleToCeiledSize(view_size_in_pixels, thumbnail_scale_));
84 rwhv->CopyFromSurface(source_rect, thumbnail_size,
85 std::move(result_callback));
86 }
87
~TabReadbackRequest()88 virtual ~TabReadbackRequest() {}
89
OnFinishGetTabThumbnailBitmap(const SkBitmap & bitmap)90 void OnFinishGetTabThumbnailBitmap(const SkBitmap& bitmap) {
91 if (bitmap.drawsNothing() || drop_after_readback_) {
92 std::move(end_callback_).Run(0.f, SkBitmap());
93 return;
94 }
95
96 SkBitmap result_bitmap = bitmap;
97 result_bitmap.setImmutable();
98 std::move(end_callback_).Run(thumbnail_scale_, bitmap);
99 }
100
SetToDropAfterReadback()101 void SetToDropAfterReadback() { drop_after_readback_ = true; }
102
103 private:
104 const float thumbnail_scale_;
105 TabReadbackCallback end_callback_;
106 bool drop_after_readback_;
107
108 base::WeakPtrFactory<TabReadbackRequest> weak_factory_{this};
109
110 DISALLOW_COPY_AND_ASSIGN(TabReadbackRequest);
111 };
112
113 // static
FromJavaObject(const JavaRef<jobject> & jobj)114 TabContentManager* TabContentManager::FromJavaObject(
115 const JavaRef<jobject>& jobj) {
116 if (jobj.is_null())
117 return nullptr;
118 return reinterpret_cast<TabContentManager*>(
119 Java_TabContentManager_getNativePtr(base::android::AttachCurrentThread(),
120 jobj));
121 }
122
TabContentManager(JNIEnv * env,jobject obj,jint default_cache_size,jint approximation_cache_size,jint compression_queue_max_size,jint write_queue_max_size,jboolean use_approximation_thumbnail,jboolean save_jpeg_thumbnails)123 TabContentManager::TabContentManager(JNIEnv* env,
124 jobject obj,
125 jint default_cache_size,
126 jint approximation_cache_size,
127 jint compression_queue_max_size,
128 jint write_queue_max_size,
129 jboolean use_approximation_thumbnail,
130 jboolean save_jpeg_thumbnails)
131 : weak_java_tab_content_manager_(env, obj) {
132 double jpeg_aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
133 chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", 1.0);
134 thumbnail_cache_ = std::make_unique<ThumbnailCache>(
135 static_cast<size_t>(default_cache_size),
136 static_cast<size_t>(approximation_cache_size),
137 static_cast<size_t>(compression_queue_max_size),
138 static_cast<size_t>(write_queue_max_size), use_approximation_thumbnail,
139 save_jpeg_thumbnails, jpeg_aspect_ratio);
140 thumbnail_cache_->AddThumbnailCacheObserver(this);
141 }
142
~TabContentManager()143 TabContentManager::~TabContentManager() {
144 }
145
Destroy(JNIEnv * env)146 void TabContentManager::Destroy(JNIEnv* env) {
147 thumbnail_cache_->RemoveThumbnailCacheObserver(this);
148 delete this;
149 }
150
SetUIResourceProvider(ui::UIResourceProvider * ui_resource_provider)151 void TabContentManager::SetUIResourceProvider(
152 ui::UIResourceProvider* ui_resource_provider) {
153 thumbnail_cache_->SetUIResourceProvider(ui_resource_provider);
154 }
155
GetLiveLayer(int tab_id)156 scoped_refptr<cc::Layer> TabContentManager::GetLiveLayer(int tab_id) {
157 return live_layer_list_[tab_id];
158 }
159
GetStaticLayer(int tab_id)160 scoped_refptr<ThumbnailLayer> TabContentManager::GetStaticLayer(int tab_id) {
161 return static_layer_cache_[tab_id];
162 }
163
GetOrCreateStaticLayer(int tab_id,bool force_disk_read)164 scoped_refptr<ThumbnailLayer> TabContentManager::GetOrCreateStaticLayer(
165 int tab_id,
166 bool force_disk_read) {
167 Thumbnail* thumbnail = thumbnail_cache_->Get(tab_id, force_disk_read, true);
168 scoped_refptr<ThumbnailLayer> static_layer = static_layer_cache_[tab_id];
169
170 if (!thumbnail || !thumbnail->ui_resource_id()) {
171 if (static_layer.get()) {
172 static_layer->layer()->RemoveFromParent();
173 static_layer_cache_.erase(tab_id);
174 }
175 return nullptr;
176 }
177
178 if (!static_layer.get()) {
179 static_layer = ThumbnailLayer::Create();
180 static_layer_cache_[tab_id] = static_layer;
181 }
182
183 static_layer->SetThumbnail(thumbnail);
184 return static_layer;
185 }
186
AttachTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & jtab,jint tab_id)187 void TabContentManager::AttachTab(JNIEnv* env,
188 const JavaParamRef<jobject>& obj,
189 const JavaParamRef<jobject>& jtab,
190 jint tab_id) {
191 TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
192 scoped_refptr<cc::Layer> layer = tab->GetContentLayer();
193 if (!layer.get())
194 return;
195
196 scoped_refptr<cc::Layer> cached_layer = live_layer_list_[tab_id];
197 if (cached_layer != layer)
198 live_layer_list_[tab_id] = layer;
199 }
200
DetachTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & jtab,jint tab_id)201 void TabContentManager::DetachTab(JNIEnv* env,
202 const JavaParamRef<jobject>& obj,
203 const JavaParamRef<jobject>& jtab,
204 jint tab_id) {
205 scoped_refptr<cc::Layer> current_layer = live_layer_list_[tab_id];
206 if (!current_layer.get()) {
207 // Empty cached layer should not exist but it is ok if it happens.
208 return;
209 }
210
211 TabAndroid* tab = TabAndroid::GetNativeTab(env, jtab);
212 scoped_refptr<cc::Layer> layer = tab->GetContentLayer();
213 // We need to remove if we're getting a detach for our current layer or we're
214 // getting a detach with NULL and we have a current layer, which means remove
215 // all layers.
216 if (current_layer.get() &&
217 (layer.get() == current_layer.get() || !layer.get())) {
218 live_layer_list_.erase(tab_id);
219 }
220 }
221
HasFullCachedThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id)222 jboolean TabContentManager::HasFullCachedThumbnail(
223 JNIEnv* env,
224 const JavaParamRef<jobject>& obj,
225 jint tab_id) {
226 return thumbnail_cache_->Get(tab_id, false, false) != nullptr;
227 }
228
GetRwhvForTab(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab)229 content::RenderWidgetHostView* TabContentManager::GetRwhvForTab(
230 JNIEnv* env,
231 const JavaParamRef<jobject>& obj,
232 const JavaParamRef<jobject>& tab) {
233 TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
234 DCHECK(tab_android);
235 const int tab_id = tab_android->GetAndroidId();
236 if (pending_tab_readbacks_.find(tab_id) != pending_tab_readbacks_.end()) {
237 return nullptr;
238 }
239
240 content::WebContents* web_contents = tab_android->web_contents();
241 DCHECK(web_contents);
242
243 content::RenderViewHost* rvh = web_contents->GetRenderViewHost();
244 if (!rvh)
245 return nullptr;
246
247 content::RenderWidgetHost* rwh = rvh->GetWidget();
248 content::RenderWidgetHostView* rwhv = rwh ? rwh->GetView() : nullptr;
249 if (!rwhv || !rwhv->IsSurfaceAvailableForCopy())
250 return nullptr;
251
252 return rwhv;
253 }
254
CaptureThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab,jfloat thumbnail_scale,jboolean write_to_cache,const base::android::JavaParamRef<jobject> & j_callback)255 void TabContentManager::CaptureThumbnail(
256 JNIEnv* env,
257 const JavaParamRef<jobject>& obj,
258 const JavaParamRef<jobject>& tab,
259 jfloat thumbnail_scale,
260 jboolean write_to_cache,
261 const base::android::JavaParamRef<jobject>& j_callback) {
262 TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
263 DCHECK(tab_android);
264 const int tab_id = tab_android->GetAndroidId();
265
266 content::RenderWidgetHostView* rwhv = GetRwhvForTab(env, obj, tab);
267 if (!rwhv) {
268 if (j_callback)
269 RunObjectCallbackAndroid(j_callback, nullptr);
270 return;
271 }
272 if (write_to_cache && !thumbnail_cache_->CheckAndUpdateThumbnailMetaData(
273 tab_id, tab_android->GetURL())) {
274 return;
275 }
276 TabReadbackCallback readback_done_callback = base::BindOnce(
277 &TabContentManager::OnTabReadback, weak_factory_.GetWeakPtr(), tab_id,
278 base::android::ScopedJavaGlobalRef<jobject>(j_callback), write_to_cache);
279 pending_tab_readbacks_[tab_id] = std::make_unique<TabReadbackRequest>(
280 rwhv, thumbnail_scale, !write_to_cache,
281 std::move(readback_done_callback));
282 }
283
CacheTabWithBitmap(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & tab,const JavaParamRef<jobject> & bitmap,jfloat thumbnail_scale)284 void TabContentManager::CacheTabWithBitmap(JNIEnv* env,
285 const JavaParamRef<jobject>& obj,
286 const JavaParamRef<jobject>& tab,
287 const JavaParamRef<jobject>& bitmap,
288 jfloat thumbnail_scale) {
289 TabAndroid* tab_android = TabAndroid::GetNativeTab(env, tab);
290 DCHECK(tab_android);
291 int tab_id = tab_android->GetAndroidId();
292 GURL url = tab_android->GetURL();
293
294 gfx::JavaBitmap java_bitmap_lock(bitmap);
295 SkBitmap skbitmap = gfx::CreateSkBitmapFromJavaBitmap(java_bitmap_lock);
296 skbitmap.setImmutable();
297
298 if (thumbnail_cache_->CheckAndUpdateThumbnailMetaData(tab_id, url))
299 OnTabReadback(tab_id, nullptr, true, thumbnail_scale, skbitmap);
300 }
301
InvalidateIfChanged(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id,const JavaParamRef<jstring> & jurl)302 void TabContentManager::InvalidateIfChanged(JNIEnv* env,
303 const JavaParamRef<jobject>& obj,
304 jint tab_id,
305 const JavaParamRef<jstring>& jurl) {
306 thumbnail_cache_->InvalidateThumbnailIfChanged(
307 tab_id, GURL(base::android::ConvertJavaStringToUTF8(env, jurl)));
308 }
309
UpdateVisibleIds(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jintArray> & priority,jint primary_tab_id)310 void TabContentManager::UpdateVisibleIds(
311 JNIEnv* env,
312 const JavaParamRef<jobject>& obj,
313 const JavaParamRef<jintArray>& priority,
314 jint primary_tab_id) {
315 std::list<int> priority_ids;
316 jsize length = env->GetArrayLength(priority);
317 jint* ints = env->GetIntArrayElements(priority, nullptr);
318 for (jsize i = 0; i < length; ++i)
319 priority_ids.push_back(static_cast<int>(ints[i]));
320
321 env->ReleaseIntArrayElements(priority, ints, JNI_ABORT);
322 thumbnail_cache_->UpdateVisibleIds(priority_ids, primary_tab_id);
323 }
324
NativeRemoveTabThumbnail(int tab_id)325 void TabContentManager::NativeRemoveTabThumbnail(int tab_id) {
326 TabReadbackRequestMap::iterator readback_iter =
327 pending_tab_readbacks_.find(tab_id);
328 if (readback_iter != pending_tab_readbacks_.end())
329 readback_iter->second->SetToDropAfterReadback();
330 thumbnail_cache_->Remove(tab_id);
331 }
332
RemoveTabThumbnail(JNIEnv * env,const JavaParamRef<jobject> & obj,jint tab_id)333 void TabContentManager::RemoveTabThumbnail(JNIEnv* env,
334 const JavaParamRef<jobject>& obj,
335 jint tab_id) {
336 NativeRemoveTabThumbnail(tab_id);
337 }
338
GetEtc1TabThumbnail(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint tab_id,const base::android::JavaParamRef<jobject> & j_callback)339 void TabContentManager::GetEtc1TabThumbnail(
340 JNIEnv* env,
341 const base::android::JavaParamRef<jobject>& obj,
342 jint tab_id,
343 const base::android::JavaParamRef<jobject>& j_callback) {
344 thumbnail_cache_->DecompressThumbnailFromFile(
345 tab_id,
346 base::BindRepeating(
347 &TabContentManager::SendThumbnailToJava, weak_factory_.GetWeakPtr(),
348 base::android::ScopedJavaGlobalRef<jobject>(j_callback),
349 /* need_downsampling */ true));
350 }
351
OnUIResourcesWereEvicted()352 void TabContentManager::OnUIResourcesWereEvicted() {
353 thumbnail_cache_->OnUIResourcesWereEvicted();
354 }
355
OnFinishedThumbnailRead(int tab_id)356 void TabContentManager::OnFinishedThumbnailRead(int tab_id) {
357 JNIEnv* env = base::android::AttachCurrentThread();
358 Java_TabContentManager_notifyListenersOfThumbnailChange(
359 env, weak_java_tab_content_manager_.get(env), tab_id);
360 }
361
OnTabReadback(int tab_id,base::android::ScopedJavaGlobalRef<jobject> j_callback,bool write_to_cache,float thumbnail_scale,const SkBitmap & bitmap)362 void TabContentManager::OnTabReadback(
363 int tab_id,
364 base::android::ScopedJavaGlobalRef<jobject> j_callback,
365 bool write_to_cache,
366 float thumbnail_scale,
367 const SkBitmap& bitmap) {
368 TabReadbackRequestMap::iterator readback_iter =
369 pending_tab_readbacks_.find(tab_id);
370
371 if (readback_iter != pending_tab_readbacks_.end())
372 pending_tab_readbacks_.erase(tab_id);
373
374 if (j_callback) {
375 SendThumbnailToJava(j_callback, write_to_cache, true, bitmap);
376 }
377
378 if (write_to_cache && thumbnail_scale > 0 && !bitmap.empty())
379 thumbnail_cache_->Put(tab_id, bitmap, thumbnail_scale);
380 }
381
SendThumbnailToJava(base::android::ScopedJavaGlobalRef<jobject> j_callback,bool need_downsampling,bool result,const SkBitmap & bitmap)382 void TabContentManager::SendThumbnailToJava(
383 base::android::ScopedJavaGlobalRef<jobject> j_callback,
384 bool need_downsampling,
385 bool result,
386 const SkBitmap& bitmap) {
387 ScopedJavaLocalRef<jobject> j_bitmap;
388 if (!bitmap.isNull() && result) {
389 // In portrait mode, we want to show thumbnails in squares.
390 // Therefore, the thumbnail saved in portrait mode needs to be cropped to
391 // a square, or it would be vertically center-aligned, and the top would
392 // be hidden.
393 // It's fine to horizontally center-align thumbnail saved in landscape
394 // mode.
395 int scale = need_downsampling ? 2 : 1;
396 double aspect_ratio = base::GetFieldTrialParamByFeatureAsDouble(
397 chrome::android::kTabGridLayoutAndroid, "thumbnail_aspect_ratio", 1.0);
398 aspect_ratio = ThumbnailCache::clampAspectRatio(aspect_ratio, 0.5, 2.0);
399 SkIRect dest_subset = {
400 0, 0, bitmap.width() / scale,
401 std::min(bitmap.height() / scale,
402 (int)(bitmap.width() / aspect_ratio / scale))};
403 j_bitmap = gfx::ConvertToJavaBitmap(skia::ImageOperations::Resize(
404 bitmap, skia::ImageOperations::RESIZE_BETTER, bitmap.width() / scale,
405 bitmap.height() / scale, dest_subset));
406 }
407 RunObjectCallbackAndroid(j_callback, j_bitmap);
408 }
409
SetCaptureMinRequestTimeForTesting(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint timeMs)410 void TabContentManager::SetCaptureMinRequestTimeForTesting(
411 JNIEnv* env,
412 const base::android::JavaParamRef<jobject>& obj,
413 jint timeMs) {
414 thumbnail_cache_->SetCaptureMinRequestTimeForTesting(timeMs);
415 }
416
GetPendingReadbacksForTesting(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj)417 jint TabContentManager::GetPendingReadbacksForTesting(
418 JNIEnv* env,
419 const base::android::JavaParamRef<jobject>& obj) {
420 return pending_tab_readbacks_.size();
421 }
422
423 // ----------------------------------------------------------------------------
424 // Native JNI methods
425 // ----------------------------------------------------------------------------
426
JNI_TabContentManager_Init(JNIEnv * env,const JavaParamRef<jobject> & obj,jint default_cache_size,jint approximation_cache_size,jint compression_queue_max_size,jint write_queue_max_size,jboolean use_approximation_thumbnail,jboolean save_jpeg_thumbnails)427 jlong JNI_TabContentManager_Init(JNIEnv* env,
428 const JavaParamRef<jobject>& obj,
429 jint default_cache_size,
430 jint approximation_cache_size,
431 jint compression_queue_max_size,
432 jint write_queue_max_size,
433 jboolean use_approximation_thumbnail,
434 jboolean save_jpeg_thumbnails) {
435 TabContentManager* manager = new TabContentManager(
436 env, obj, default_cache_size, approximation_cache_size,
437 compression_queue_max_size, write_queue_max_size,
438 use_approximation_thumbnail, save_jpeg_thumbnails);
439 return reinterpret_cast<intptr_t>(manager);
440 }
441
442 } // namespace android
443