Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..e483b1f67aeda4fa79980f0dcfa584ed32c1ef3e
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ThumbnailTabHelper.java
@@ -0,0 +1,335 @@
+// Copyright 2015 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.
+
+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 com.google.android.apps.chrome.R;
+
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.EmptyTabObserver;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.TabObserver;
+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;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Handles capturing most visited thumbnails for a tab.
+ */
+public class ThumbnailTabHelper {
+
+ private static final String TAG = "ThumbnailTabHelper";
+
+ /** The general motivation for this value is giving the scrollbar fadeout
+ * animation sufficient time to finish before the capture executes. */
+ private static final int THUMBNAIL_CAPTURE_DELAY_MS = 350;
+
+ private final Tab mTab;
+ private final Handler mHandler;
+
+ private final int mThumbnailWidth;
+ private final int mThumbnailHeight;
+
+ private ContentViewCore mContentViewCore;
+ private boolean mThumbnailCapturedForLoad;
+ private boolean mIsRenderViewHostReady;
+ private boolean mWasRenderViewHostReady;
+
+ private final Runnable mThumbnailRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // http://crbug.com/461506 : Do not get thumbnail unless render view host is ready.
+ if (!mIsRenderViewHostReady) return;
+
+ if (mThumbnailCapturedForLoad) return;
+ // Prevent redundant thumbnail capture attempts.
+ mThumbnailCapturedForLoad = true;
+ if (!canUpdateHistoryThumbnail()) {
+ // Allow a hidden tab to re-attempt capture in the future via |show()|.
+ mThumbnailCapturedForLoad = !mTab.isHidden();
+ return;
+ }
+ if (!shouldUpdateThumbnail()) return;
+
+ // Scale the image so we're not copying more than we need to (from
+ // the GPU).
+ int[] dim = new int[] {
+ mTab.getWidth(), mTab.getHeight()
+ };
+ MathUtils.scaleToFitTargetSize(dim, mThumbnailWidth, 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, dim[0], dim[1]),
+ mTab.getContentViewCore(), Bitmap.Config.ARGB_8888, bitmapCallback);
+ }
+ };
+
+ private final TabObserver mTabObserver = new EmptyTabObserver() {
+ @Override
+ public void onContentChanged(Tab tab) {
+ ThumbnailTabHelper.this.onContentChanged();
+ }
+
+ @Override
+ public void onCrash(Tab tab, boolean sadTabShown) {
+ cancelThumbnailCapture();
+ }
+
+ @Override
+ public void onPageLoadStarted(Tab tab) {
+ cancelThumbnailCapture();
+ mThumbnailCapturedForLoad = false;
+ }
+
+ @Override
+ public void onPageLoadFinished(Tab tab) {
+ rescheduleThumbnailCapture();
+ }
+
+ @Override
+ public void onPageLoadFailed(Tab tab, int errorCode) {
+ cancelThumbnailCapture();
+ }
+
+ @Override
+ public void onShown(Tab tab) {
+ // For tabs opened in the background, they may finish loading prior to becoming visible
+ // and the thumbnail capture triggered as part of load finish will be skipped as the
+ // tab has nothing rendered. To handle this case, we also attempt thumbnail capture
+ // when showing the tab to give it a better chance to have valid content.
+ rescheduleThumbnailCapture();
+ }
+
+ @Override
+ public void onClosingStateChanged(Tab tab, boolean closing) {
+ if (closing) cancelThumbnailCapture();
+ }
+
+ @Override
+ public void onDestroyed(Tab tab) {
+ mTab.removeObserver(mTabObserver);
+ if (mContentViewCore != null) {
+ mContentViewCore.removeGestureStateListener(mGestureListener);
+ mContentViewCore = null;
+ }
+ }
+
+ @Override
+ public void onDidStartProvisionalLoadForFrame(
+ Tab tab, long frameId, long parentFrameId, boolean isMainFrame, String validatedUrl,
+ boolean isErrorPage, boolean isIframeSrcdoc) {
+ if (isMainFrame) {
+ mWasRenderViewHostReady = mIsRenderViewHostReady;
+ mIsRenderViewHostReady = false;
+ }
+ }
+
+ @Override
+ public void onDidFailLoad(
+ Tab tab, boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
+ String description, String failingUrl) {
+ // For a case that URL overriding happens, we should recover |mIsRenderViewHostReady| to
+ // its old value to enable capturing thumbnail of the current page.
+ // If this failure shows an error page, capturing thumbnail will be denied anyway in
+ // canUpdateHistoryThumbnail().
+ if (isProvisionalLoad && isMainFrame) mIsRenderViewHostReady = mWasRenderViewHostReady;
+ }
+
+ @Override
+ public void onDidCommitProvisionalLoadForFrame(
+ Tab tab, long frameId, boolean isMainFrame, String url, int transitionType) {
+ if (isMainFrame) mIsRenderViewHostReady = true;
+ }
+ };
+
+ private GestureStateListener mGestureListener = new GestureStateListener() {
+ @Override
+ public void onFlingStartGesture(int vx, int vy, int scrollOffsetY, int scrollExtentY) {
+ cancelThumbnailCapture();
+ }
+
+ @Override
+ public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
+ rescheduleThumbnailCapture();
+ }
+
+ @Override
+ public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
+ cancelThumbnailCapture();
+ }
+
+ @Override
+ public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+ rescheduleThumbnailCapture();
+ }
+ };
+
+ /**
+ * Creates a thumbnail tab helper for the given tab.
+ * @param tab The Tab whose thumbnails will be generated by this helper.
+ */
+ public static void createForTab(Tab tab) {
+ new ThumbnailTabHelper(tab);
+ }
+
+ /**
+ * Constructs the thumbnail tab helper for a given Tab.
+ * @param tab The Tab whose thumbnails will be generated by this helper.
+ */
+ private ThumbnailTabHelper(Tab tab) {
+ mTab = tab;
+ mTab.addObserver(mTabObserver);
+
+ 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);
+
+ onContentChanged();
+ }
+
+ private void onContentChanged() {
+ if (mContentViewCore != null) {
+ mContentViewCore.removeGestureStateListener(mGestureListener);
+ }
+
+ mContentViewCore = mTab.getContentViewCore();
+ if (mContentViewCore != null) {
+ mContentViewCore.addGestureStateListener(mGestureListener);
+ nativeInitThumbnailHelper(mContentViewCore.getWebContents());
+ }
+ }
+
+ private ChromeActivity getActivity() {
+ WindowAndroid window = mTab.getWindowAndroid();
+ return (ChromeActivity) window.getActivity().get();
+ }
+
+ private void cancelThumbnailCapture() {
+ mHandler.removeCallbacks(mThumbnailRunnable);
+ }
+
+ private void rescheduleThumbnailCapture() {
+ if (mThumbnailCapturedForLoad) return;
+ cancelThumbnailCapture();
+ // Capture will be rescheduled when the GestureStateListener receives a
+ // scroll or fling end notification.
+ if (mTab.getContentViewCore() != null
+ && mTab.getContentViewCore().isScrollInProgress()) {
+ return;
+ }
+ 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)
+ || url.startsWith(UrlConstants.CHROME_NATIVE_SCHEME)) {
+ return false;
+ }
+ return mTab.isReady()
+ && !mTab.isShowingErrorPage()
+ && !mTab.isHidden()
+ && !mTab.isShowingSadTab()
+ && !mTab.isShowingInterstitialPage()
+ && mTab.getProgress() == 100
+ && mTab.getWidth() > 0
+ && 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);
+ }
+ }
+
+ 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);
+}

Powered by Google App Engine
This is Rietveld 408576698