diff options
-rw-r--r-- | chrome/android/java/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java | 122 | ||||
-rw-r--r-- | chrome/browser/android/tab/thumbnail_tab_helper_android.cc | 161 |
2 files changed, 136 insertions, 147 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java index 440776c..2a50fce 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java @@ -5,21 +5,13 @@ package org.chromium.chrome.browser.tab; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; import android.os.Handler; import android.text.TextUtils; -import android.util.Log; +import org.chromium.base.annotations.CalledByNative; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.UrlConstants; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.util.MathUtils; -import org.chromium.content.browser.ContentReadbackHandler; import org.chromium.content.browser.ContentViewCore; import org.chromium.content_public.browser.GestureStateListener; import org.chromium.content_public.browser.WebContents; @@ -39,13 +31,14 @@ public class ThumbnailTabHelper { private final Tab mTab; private final Handler mHandler; - private final int mThumbnailWidth; - private final int mThumbnailHeight; + private final int mThumbnailWidthDp; + private final int mThumbnailHeightDp; private ContentViewCore mContentViewCore; private boolean mThumbnailCapturedForLoad; private boolean mIsRenderViewHostReady; private boolean mWasRenderViewHostReady; + private String mRequestedUrl; private final Runnable mThumbnailRunnable = new Runnable() { @Override @@ -61,33 +54,11 @@ public class ThumbnailTabHelper { mThumbnailCapturedForLoad = !mTab.isHidden(); return; } - if (!shouldUpdateThumbnail()) return; - - int snapshotWidth = Math.min(mTab.getWidth(), mThumbnailWidth); - int snapshotHeight = Math.min(mTab.getHeight(), mThumbnailHeight); - - ContentReadbackHandler readbackHandler = getActivity().getContentReadbackHandler(); - if (readbackHandler == null || mTab.getContentViewCore() == null) return; - final String requestedUrl = mTab.getUrl(); - ContentReadbackHandler.GetBitmapCallback bitmapCallback = - new ContentReadbackHandler.GetBitmapCallback() { - @Override - public void onFinishGetBitmap(Bitmap bitmap, int response) { - // Ensure that the URLs match for the requested page, and ensure - // that the page is still valid for thumbnail capturing (i.e. - // not showing an error page). - if (bitmap == null - || !TextUtils.equals(requestedUrl, mTab.getUrl()) - || !mThumbnailCapturedForLoad - || !canUpdateHistoryThumbnail()) { - return; - } - updateHistoryThumbnail(bitmap); - bitmap.recycle(); - } - }; - readbackHandler.getContentBitmapAsync(1, new Rect(0, 0, snapshotWidth, snapshotHeight), - mTab.getContentViewCore(), Bitmap.Config.ARGB_8888, bitmapCallback); + if (mTab.getWebContents() == null) return; + + mRequestedUrl = mTab.getUrl(); + nativeCaptureThumbnail(ThumbnailTabHelper.this, mTab.getWebContents(), + mThumbnailWidthDp, mThumbnailHeightDp); } }; @@ -196,7 +167,7 @@ public class ThumbnailTabHelper { * @param tab The Tab whose thumbnails will be generated by this helper. */ public static void createForTab(Tab tab) { - new ThumbnailTabHelper(tab); + if (!tab.isIncognito()) new ThumbnailTabHelper(tab); } /** @@ -210,8 +181,11 @@ public class ThumbnailTabHelper { mHandler = new Handler(); Resources res = tab.getWindowAndroid().getApplicationContext().getResources(); - mThumbnailWidth = res.getDimensionPixelSize(R.dimen.most_visited_thumbnail_width); - mThumbnailHeight = res.getDimensionPixelSize(R.dimen.most_visited_thumbnail_height); + float density = res.getDisplayMetrics().density; + mThumbnailWidthDp = Math.round( + res.getDimension(R.dimen.most_visited_thumbnail_width) / density); + mThumbnailHeightDp = Math.round( + res.getDimension(R.dimen.most_visited_thumbnail_height) / density); onContentChanged(); } @@ -249,17 +223,6 @@ public class ThumbnailTabHelper { mHandler.postDelayed(mThumbnailRunnable, THUMBNAIL_CAPTURE_DELAY_MS); } - private boolean shouldUpdateThumbnail() { - return nativeShouldUpdateThumbnail(mTab.getProfile(), mTab.getUrl()); - } - - private void updateThumbnail(Bitmap bitmap) { - if (mTab.getContentViewCore() != null) { - final boolean atTop = mTab.getContentViewCore().computeVerticalScrollOffset() == 0; - nativeUpdateThumbnail(mTab.getWebContents(), bitmap, atTop); - } - } - private boolean canUpdateHistoryThumbnail() { String url = mTab.getUrl(); if (url.startsWith(UrlConstants.CHROME_SCHEME) @@ -276,52 +239,17 @@ public class ThumbnailTabHelper { && mTab.getHeight() > 0; } - private void updateHistoryThumbnail(Bitmap bitmap) { - if (mTab.isIncognito()) return; - - // TODO(yusufo): It will probably be faster and more efficient on resources to do this on - // the native side, but the thumbnail_generator code has to be refactored a bit to allow - // creating a downsized version of a bitmap progressively. - if (bitmap.getWidth() != mThumbnailWidth - || bitmap.getHeight() != mThumbnailHeight - || bitmap.getConfig() != Config.ARGB_8888) { - try { - int[] dim = new int[] { - bitmap.getWidth(), bitmap.getHeight() - }; - // If the thumbnail size is small compared to the bitmap size downsize in - // two stages. This makes the final quality better. - float scale = Math.max( - (float) mThumbnailWidth / dim[0], - (float) mThumbnailHeight / dim[1]); - int adjustedWidth = (scale < 1) - ? mThumbnailWidth * (int) (1 / Math.sqrt(scale)) : mThumbnailWidth; - int adjustedHeight = (scale < 1) - ? mThumbnailHeight * (int) (1 / Math.sqrt(scale)) : mThumbnailHeight; - scale = MathUtils.scaleToFitTargetSize(dim, adjustedWidth, adjustedHeight); - // Horizontally center the source bitmap in the final result. - float leftOffset = (adjustedWidth - dim[0]) / 2.0f / scale; - Bitmap tmpBitmap = Bitmap.createBitmap(adjustedWidth, - adjustedHeight, Config.ARGB_8888); - Canvas c = new Canvas(tmpBitmap); - c.scale(scale, scale); - c.drawBitmap(bitmap, leftOffset, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); - if (scale < 1) { - tmpBitmap = Bitmap.createScaledBitmap(tmpBitmap, - mThumbnailWidth, mThumbnailHeight, true); - } - updateThumbnail(tmpBitmap); - tmpBitmap.recycle(); - } catch (OutOfMemoryError ex) { - Log.w(TAG, "OutOfMemoryError while updating the history thumbnail."); - } - } else { - updateThumbnail(bitmap); - } + @CalledByNative + private boolean shouldSaveCapturedThumbnail() { + // Ensure that the URLs match for the requested page, and ensure + // that the page is still valid for thumbnail capturing (i.e. + // not showing an error page). + return TextUtils.equals(mRequestedUrl, mTab.getUrl()) + && mThumbnailCapturedForLoad + && canUpdateHistoryThumbnail(); } private static native void nativeInitThumbnailHelper(WebContents webContents); - private static native void nativeUpdateThumbnail( - WebContents webContents, Bitmap bitmap, boolean atTop); - private static native boolean nativeShouldUpdateThumbnail(Profile profile, String url); + private static native void nativeCaptureThumbnail(ThumbnailTabHelper thumbnailTabHelper, + WebContents webContents, int thumbnailWidthDp, int thumbnailHeightDp); } diff --git a/chrome/browser/android/tab/thumbnail_tab_helper_android.cc b/chrome/browser/android/tab/thumbnail_tab_helper_android.cc index 17c0cae..c93d41c 100644 --- a/chrome/browser/android/tab/thumbnail_tab_helper_android.cc +++ b/chrome/browser/android/tab/thumbnail_tab_helper_android.cc @@ -5,23 +5,108 @@ #include "chrome/browser/android/tab/thumbnail_tab_helper_android.h" #include "base/android/jni_android.h" -#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" #include "base/memory/ref_counted.h" -#include "chrome/browser/history/top_sites_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_android.h" +#include "chrome/browser/thumbnails/simple_thumbnail_crop.h" #include "chrome/browser/thumbnails/thumbnail_service.h" #include "chrome/browser/thumbnails/thumbnail_service_factory.h" #include "chrome/browser/thumbnails/thumbnail_tab_helper.h" -#include "components/history/core/browser/top_sites.h" +#include "chrome/browser/thumbnails/thumbnailing_algorithm.h" +#include "chrome/browser/thumbnails/thumbnailing_context.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 "jni/ThumbnailTabHelper_jni.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/android/java_bitmap.h" -#include "ui/gfx/color_utils.h" #include "ui/gfx/image/image_skia.h" #include "url/gurl.h" +using thumbnails::ThumbnailingAlgorithm; +using thumbnails::ThumbnailingContext; +using thumbnails::ThumbnailService; + +namespace { + +const int kScrollbarWidthDp = 6; + +void UpdateThumbnail(const ThumbnailingContext& context, + const SkBitmap& thumbnail) { + gfx::Image image = gfx::Image::CreateFrom1xBitmap(thumbnail); + context.service->SetPageThumbnail(context, image); +} + +void ProcessCapturedBitmap( + const base::android::ScopedJavaGlobalRef<jobject>& jthumbnail_tab_helper, + scoped_refptr<ThumbnailingContext> context, + scoped_refptr<ThumbnailingAlgorithm> algorithm, + const SkBitmap& bitmap, + content::ReadbackResponse response) { + if (response != content::READBACK_SUCCESS) + return; + + // On success, we must be on the UI thread (on failure because of shutdown we + // are not on the UI thread). + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + JNIEnv* env = base::android::AttachCurrentThread(); + if (!Java_ThumbnailTabHelper_shouldSaveCapturedThumbnail( + env, jthumbnail_tab_helper.obj())) { + return; + } + + algorithm->ProcessBitmap(context, base::Bind(&UpdateThumbnail), bitmap); +} + +void CaptureThumbnailInternal( + const base::android::ScopedJavaGlobalRef<jobject>& jthumbnail_tab_helper, + content::WebContents* web_contents, + scoped_refptr<ThumbnailingContext> context, + scoped_refptr<ThumbnailingAlgorithm> algorithm, + const gfx::Size& thumbnail_size) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::RenderWidgetHost* render_widget_host = + web_contents->GetRenderViewHost(); + content::RenderWidgetHostView* view = render_widget_host->GetView(); + if (!view) + return; + if (!view->IsSurfaceAvailableForCopy()) + return; + + gfx::Rect copy_rect = gfx::Rect(view->GetViewBounds().size()); + // Clip the pixels that will commonly hold a scrollbar, which looks bad in + // thumbnails. + copy_rect.Inset(0, 0, kScrollbarWidthDp, 0); + if (copy_rect.IsEmpty()) + return; + + ui::ScaleFactor scale_factor = + ui::GetSupportedScaleFactor( + ui::GetScaleFactorForNativeView(view->GetNativeView())); + context->clip_result = algorithm->GetCanvasCopyInfo( + copy_rect.size(), + scale_factor, + ©_rect, + &context->requested_copy_size); + + // Workaround for a bug where CopyFromBackingStore() accepts different input + // units on Android (DIP) vs on other platforms (pixels). + // TODO(newt): remove this line once https://crbug.com/540497 is fixed. + context->requested_copy_size = thumbnail_size; + + render_widget_host->CopyFromBackingStore( + copy_rect, context->requested_copy_size, + base::Bind(&ProcessCapturedBitmap, jthumbnail_tab_helper, context, + algorithm), + kN32_SkColorType); +} + +} // namespace + // static bool RegisterThumbnailTabHelperAndroid(JNIEnv* env) { return RegisterNativesImpl(env); @@ -44,57 +129,33 @@ static void InitThumbnailHelper(JNIEnv* env, thumbnail_tab_helper->set_enabled(false); } -static jboolean ShouldUpdateThumbnail(JNIEnv* env, - const JavaParamRef<jclass>& clazz, - const JavaParamRef<jobject>& jprofile, - const JavaParamRef<jstring>& jurl) { - Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile); - - GURL url(base::android::ConvertJavaStringToUTF8(env, jurl)); - scoped_refptr<thumbnails::ThumbnailService> thumbnail_service = - ThumbnailServiceFactory::GetForProfile(profile); - return (thumbnail_service.get() != NULL && - thumbnail_service->ShouldAcquirePageThumbnail(url)); -} - -static void UpdateThumbnail(JNIEnv* env, - const JavaParamRef<jclass>& clazz, - const JavaParamRef<jobject>& jweb_contents, - const JavaParamRef<jobject>& bitmap, - jboolean jat_top) { +static void CaptureThumbnail(JNIEnv* env, + const JavaParamRef<jclass>& clazz, + const JavaParamRef<jobject>& jthumbnail_tab_helper, + const JavaParamRef<jobject>& jweb_contents, + jint thumbnail_width_dp, + jint thumbnail_height_dp) { content::WebContents* web_contents = content::WebContents::FromJavaWebContents(jweb_contents); DCHECK(web_contents); + Profile* profile = + Profile::FromBrowserContext(web_contents->GetBrowserContext()); - Profile* profile = Profile::FromBrowserContext( - web_contents->GetBrowserContext()); - - gfx::JavaBitmap bitmap_lock(bitmap); - SkBitmap sk_bitmap; - gfx::Size size = bitmap_lock.size(); - SkColorType color_type = kN32_SkColorType; - sk_bitmap.setInfo( - SkImageInfo::Make(size.width(), size.height(), - color_type, kPremul_SkAlphaType), 0); - sk_bitmap.setPixels(bitmap_lock.pixels()); - - // TODO(nileshagrawal): Refactor this. - // We were using some non-public methods from ThumbnailTabHelper. We need to - // either add android specific logic to ThumbnailTabHelper or create our own - // helper which is driven by the java app (will need to pull out some logic - // from ThumbnailTabHelper to a common class). - scoped_refptr<history::TopSites> ts = TopSitesFactory::GetForProfile(profile); - if (!ts) + scoped_refptr<ThumbnailService> thumbnail_service = + ThumbnailServiceFactory::GetForProfile(profile); + if (thumbnail_service.get() == nullptr || + !thumbnail_service->ShouldAcquirePageThumbnail( + web_contents->GetLastCommittedURL())) { return; + } - // Compute the thumbnail score. - ThumbnailScore score; - score.at_top = jat_top; - score.boring_score = color_utils::CalculateBoringScore(sk_bitmap); - score.good_clipping = true; - score.load_completed = !web_contents->IsLoading(); + const gfx::Size thumbnail_size(thumbnail_width_dp, thumbnail_height_dp); + scoped_refptr<ThumbnailingAlgorithm> algorithm( + new thumbnails::SimpleThumbnailCrop(thumbnail_size)); - gfx::Image image = gfx::Image::CreateFrom1xBitmap(sk_bitmap); - const GURL& url = web_contents->GetURL(); - ts->SetPageThumbnail(url, image, score); + scoped_refptr<ThumbnailingContext> context(new ThumbnailingContext( + web_contents, thumbnail_service.get(), false /*load_interrupted*/)); + CaptureThumbnailInternal( + base::android::ScopedJavaGlobalRef<jobject>(env, jthumbnail_tab_helper), + web_contents, context, algorithm, thumbnail_size); } |