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/BackgroundContentViewHelper.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/BackgroundContentViewHelper.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/BackgroundContentViewHelper.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/BackgroundContentViewHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9c95a3fc1fcaffc33587afe4f5f51424a9bf7e8
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/BackgroundContentViewHelper.java
@@ -0,0 +1,897 @@
+// 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.app.Activity;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.TextUtils;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.FieldTrialList;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.ChromeVersionInfo;
+import org.chromium.chrome.browser.ContentViewUtil;
+import org.chromium.chrome.browser.EmptyTabObserver;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.components.web_contents_delegate_android.WebContentsDelegateAndroid;
+import org.chromium.content.browser.ContentView;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.RenderCoordinates;
+import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.content_public.common.Referrer;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Responsible for background content view creation and management.
+ */
+public class BackgroundContentViewHelper extends EmptyTabObserver {
+ private static final String GOOGLE_PREVIEW_SERVER_PREFIX = "icl";
+
+ // Google preview staging and canary servers. Only to be used in dev builds.
+ private static final String GOOGLE_PREVIEW_SERVER_SUFFIX = ".googleusercontent.com";
+
+ private static final String GWS_CHROME_JOINT_EXPERIMENT_ID_STRING = "gcjeid";
+
+ private static final String GOOGLE_DOMAIN_PREFIX = "www.google.";
+
+ // Instant search clicks swap reasons.
+ // IMPORTANT: Do not renumber any existing constants (except the BOUNDARY ones). Keep these in
+ // sync with histograms.xml file.
+ private static final int SWAP_REASON_REGULAR = 0;
+ private static final int SWAP_REASON_TIMEOUT = 1;
+ private static final int SWAP_REASON_ABORT_ON_NAVIGATE = 2;
+ private static final int SWAP_REASON_ON_PREVIEW_FAIL = 3;
+ private static final int SWAP_REASON_ORIGINAL_FAIL = 4;
+ private static final int SWAP_REASON_FORCE = 5;
+ private static final int SWAP_REASON_BOUNDARY = 6;
+
+ // Instant search clicks scroll state while swapping.
+ // IMPORTANT: Do not renumber any existing constants (except the BOUNDARY ones). Keep these in
+ // sync with histograms.xml file.
+ private static final int PREVIEW_SCROLL_STATE_NO_SCROLL = 0;
+ private static final int PREVIEW_SCROLL_STATE_SCROLL = 1;
+ private static final int PREVIEW_SCROLL_STATE_SCROLL_AT_BOTTOM = 2;
+ private static final int PREVIEW_SCROLL_STATE_BOUNDARY = 3;
+
+ // Instant search clicks experiment state.
+ private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED = 0;
+ private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_ENABLED = 1;
+ private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL = 2;
+
+ /**
+ * The maximum amount of time to wait for the background page to swap. We force the swap after
+ * this timeout.
+ */
+ private final int mSwapTimeoutMs;
+
+ /**
+ * The minimum number of renderer frames needed before swap is triggered.
+ */
+ private final int mMinRendererFramesNeededForSwap;
+
+ // Native pointer corresponding to the current object.
+ private long mNativeBackgroundContentViewHelperPtr;
+
+ // The content view core created to load url in background.
+ private ContentViewCore mContentViewCore;
+
+ // Android window used to created content view.
+ private final WindowAndroid mWindowAndroid;
+
+ // The webcontents observer on current content view of the tab.
+ private WebContentsObserver mCurrentViewWebContentsObserver;
+
+ // The webcontents observer that listens for events to swap in the new WebContents.
+ private WebContentsObserver mWebContentsObserver;
+
+ // The webcontents delegate that gets notified when background view is ready to be swapped.
+ private final WebContentsDelegateAndroid mWebContentsDelegate;
+
+ // Whether or not the preview page finished loading.
+ private boolean mPreviewLoaded = false;
+
+ // Whether or not the background view has painted anything non-empty.
+ private boolean mPaintedNonEmpty = false;
+
+ private int mNumRendererFramesReceived = 0;
+
+ // Whether or not the swap is in progress. We will be in this state while syncing scroll offsets
+ // between content views.
+ private boolean mSwapInProgress = false;
+
+ // Whether or not the background view started loading content.
+ private boolean mDidStartLoad = false;
+
+ // Whether or not the background view finished loading.
+ private boolean mDidFinishLoad = false;
+
+ // The tab associated with the background view.
+ private final Tab mTab;
+
+ // The delegate who is notified when background content view is ready.
+ private final BackgroundContentViewDelegate mDelegate;
+
+ // GestureStateListener attached to the background contentView.
+ private final GestureStateListener mBackgroundGestureStateListener;
+
+ // GestureStateListener attached to the current contentView of tab.
+ private final GestureStateListener mCurrentViewGestureStateListener;
+
+ private final Handler mHandler;
+
+ private final Runnable mSwapOnTimeout = new Runnable() {
+ @Override
+ public void run() {
+ forceSwappingContentViews();
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap",
+ SWAP_REASON_TIMEOUT, SWAP_REASON_BOUNDARY);
+ }
+ };
+
+ private long mBackgroundLoadStartTimeStampMs;
+
+ /**
+ * Whether or not to record 'instant search clicks' uma for the current loaded page in tab.
+ * This is set to true, for instant search clicks enabled urls(preview urls) and also for the
+ * 'would have been' instant search click enabled urls.
+ */
+ private boolean mRecordUma = false;
+
+ private final int mExperimentGroup;
+
+ /**
+ * The recent load progress before swapping. This will be used to return the progress after
+ * swapping the page views.
+ */
+ private int mLoadProgress;
+
+ /**
+ * Whether we transferred pagescale from preview to original.
+ */
+ private boolean mOriginalPageZoomed = false;
+
+ /**
+ * The delegate that is notified when BackgroundContentView is ready to be swapped in.
+ */
+ public interface BackgroundContentViewDelegate {
+ /**
+ * Called when the background content view has drawn non-empty screen..
+ * @param cvc The background ContentViewCore that is ready to be swapped in.
+ * @param didStartLoad Whether WebContentsObserver::DidStartProvisionalLoadForFrame() has
+ * been called for background ContentViewCore.
+ * @param didFinishLoad Whether WebContentsObserver::DidFinishLoad() has already been
+ * called for background {@link ContentViewCore}.
+ * @param progress The recent load progress that was received for the
+ * {@link ContentView}.
+ */
+ void onBackgroundViewReady(
+ ContentViewCore cvc, boolean didStartLoad, boolean didFinishLoad, int progress);
+
+ /**
+ * Called to notify the load progress of backgrounc content view.
+ * @param progress The recent load progress that was received for the
+ * {@link ContentView}.
+ */
+ void onLoadProgressChanged(int progress);
+ }
+
+ /**
+ * Creates an instance of BackgroundContentViewHelper.
+ * @param window An instance of a {@link WindowAndroid}.
+ * @param tab The {@link ChromeTab} associated with current backgruond view.
+ * @param delegate The {@link BackgroundContentViewDelegate} to notify when backgruond view is
+ * ready to swap in.
+ */
+ public BackgroundContentViewHelper(WindowAndroid windowAndroid, Tab tab,
+ BackgroundContentViewDelegate delegate) {
+ mWindowAndroid = windowAndroid;
+ mNativeBackgroundContentViewHelperPtr = nativeInit();
+
+ mTab = tab;
+ assert delegate != null;
+ mDelegate = delegate;
+ mTab.addObserver(this);
+ mBackgroundGestureStateListener = new GestureStateListener() {
+ @Override
+ public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) {
+ syncPageStateAndSwapIfReady();
+ }
+ };
+
+ mCurrentViewGestureStateListener = new GestureStateListener() {
+ @Override
+ public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
+ syncPageStateAndSwapIfReady();
+ }
+
+ @Override
+ public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+ syncPageStateAndSwapIfReady();
+ }
+
+ @Override
+ public void onSingleTap(boolean consumed, int x, int y) {
+ recordSingleTap();
+ }
+ };
+
+ mWebContentsDelegate = new WebContentsDelegateAndroid() {
+ @Override
+ public void onLoadProgressChanged(int progress) {
+ mLoadProgress = progress;
+ mDelegate.onLoadProgressChanged(progress);
+ trySwappingBackgroundView();
+ }
+ };
+ mHandler = new Handler();
+
+ mSwapTimeoutMs = nativeGetSwapTimeoutMs(mNativeBackgroundContentViewHelperPtr);
+ mMinRendererFramesNeededForSwap =
+ nativeGetNumFramesNeededForSwap(mNativeBackgroundContentViewHelperPtr);
+ mBackgroundLoadStartTimeStampMs = System.currentTimeMillis();
+ mExperimentGroup = getExperimentGroup();
+ }
+
+ /*
+ * @return Experiment group that the current user fall into.
+ */
+ private int getExperimentGroup() {
+ if (!DeviceClassManager.enableInstantSearchClicks()) {
+ return INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED;
+ }
+
+ String instantSearchClicksFieldTrialValue =
+ FieldTrialList.findFullName("InstantSearchClicks");
+ if (TextUtils.equals(instantSearchClicksFieldTrialValue, "Enabled")
+ || TextUtils.equals(instantSearchClicksFieldTrialValue,
+ "InstantSearchClicksEnabled")) {
+ return INSTANT_SEARCH_CLICKS_EXPERIMENT_ENABLED;
+ } else if (TextUtils.equals(instantSearchClicksFieldTrialValue, "Control")) {
+ return INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL;
+ }
+ return INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED;
+ }
+
+ /**
+ * @return True iff content height of original page is greater than preview page.
+ */
+ private boolean hasBackgroundPageLoadedMoreThanPreview() {
+ if (!mPaintedNonEmpty || mTab.getContentViewCore() == null) return false;
+ return getContentViewCore().getContentHeightCss()
+ > mTab.getContentViewCore().getContentHeightCss();
+ }
+
+ /**
+ * @return True iff original page is loading in background content view which is not swapped in.
+ */
+ public boolean hasPendingBackgroundPage() {
+ return mContentViewCore != null;
+ }
+
+ /**
+ * @return True iff swapping is in progress. This is true till the content view is completely
+ * swapped in.
+ */
+ public boolean isPageSwappingInProgress() {
+ return mSwapInProgress;
+ }
+
+ /**
+ * @return The {@link ContentViewCore} associated with the current background page.
+ */
+ public ContentViewCore getContentViewCore() {
+ return mContentViewCore;
+ }
+
+ /** @return The recent progress of background content view. */
+ public int getProgress() {
+ return mLoadProgress;
+ }
+
+ /**
+ * Loads the specified URL in the background content view.
+ * @param url the url to load.
+ */
+ private void loadUrl(String url) {
+ if (mNativeBackgroundContentViewHelperPtr == 0) return;
+
+ if (mContentViewCore == null) return;
+
+ mContentViewCore.onShow();
+
+ LoadUrlParams loadUrlParams = new LoadUrlParams(url);
+ // Use the referrer of preview url as referrer for original url.
+ // We are always loading url during onDidStartProvisionalLoadForFrame of preview URL, where
+ // load is not yet committed and so is the navigation entry. The current active
+ // navigation entry is still the referrer for preview url.
+ String referrerString = mTab.getWebContents()
+ .getNavigationController().getOriginalUrlForVisibleNavigationEntry();
+ Uri referrer = getUri(referrerString);
+ String referrerHost = referrer.getHost();
+ if (referrerHost == null
+ || TextUtils.indexOf(referrerHost, GOOGLE_DOMAIN_PREFIX) != 0) {
+ referrerHost = "www.google.com";
+ }
+ // We come here only through google.com and google.com always sends the referrer with
+ // value at least equal to the origin (and full url in http requests).
+ // Add 'gcjeid' param to referrer so that the PLT metrics are logged in a different bucket.
+ // PLT metrics are logged in a different bucket if the referer contains 'gcjeid' param.
+ // For Experiment: gcjeid=19
+ String referrerTrimmed = "http://" + referrerHost + "/?"
+ + GWS_CHROME_JOINT_EXPERIMENT_ID_STRING + "=19";
+ loadUrlParams.setReferrer(new Referrer(referrerTrimmed, 0 /* WebReferrerPolicyAlways */));
+ mContentViewCore.getWebContents().getNavigationController().loadUrl(loadUrlParams);
+ }
+
+ /**
+ * Releases the ownership of the content view created. Also, resets the whole state of the
+ * current object.
+ */
+ private ContentViewCore releaseContentViewCore() {
+ mTab.detachOverlayContentViewCore(mContentViewCore);
+
+ ContentViewCore originalContentViewCore = mContentViewCore;
+ if (mContentViewCore != null && mNativeBackgroundContentViewHelperPtr != 0) {
+ nativeReleaseWebContents(mNativeBackgroundContentViewHelperPtr);
+ }
+ reset();
+ return originalContentViewCore;
+ }
+
+ /**
+ * Destroys the contentview and the corresponding native object.
+ */
+ private void destroy() {
+ if (mCurrentViewWebContentsObserver != null) {
+ mCurrentViewWebContentsObserver.destroy();
+ mCurrentViewWebContentsObserver = null;
+ }
+ destroyContentViewCore();
+
+ if (mNativeBackgroundContentViewHelperPtr == 0) return;
+
+ nativeDestroy(mNativeBackgroundContentViewHelperPtr);
+ mNativeBackgroundContentViewHelperPtr = 0;
+ }
+
+ /**
+ * Creates a new {@link ContentViewCore} and loads the given url.
+ * @param url Url to load.
+ */
+ private void createContentViewCoreWithUrl(String url) {
+ if (url == null || mNativeBackgroundContentViewHelperPtr == 0) return;
+
+ destroyContentViewCore();
+
+ Activity activity = mWindowAndroid.getActivity().get();
+ mContentViewCore = new ContentViewCore(activity);
+ ContentView cv = new ContentView(activity, mContentViewCore);
+ mContentViewCore.initialize(cv, cv,
+ ContentViewUtil.createWebContents(mTab.isIncognito(), false), mWindowAndroid);
+
+ // The renderer should be set with the height considering the top controls(non-fullscreen
+ // mode).
+ mContentViewCore.setTopControlsHeight(mContentViewCore.getTopControlsHeightPix(), true);
+ mWebContentsObserver = new WebContentsObserver(mContentViewCore.getWebContents()) {
+ @Override
+ public void didFirstVisuallyNonEmptyPaint() {
+ mPaintedNonEmpty = true;
+ trySwappingBackgroundView();
+ }
+ @Override
+ public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId,
+ boolean isMainFrame, String validatedUrl, boolean isErrorPage,
+ boolean isIframeSrcdoc) {
+ if (isMainFrame) mDidStartLoad = true;
+ }
+
+ @Override
+ public void didFinishLoad(long frameId, String validatedUrl, boolean isMainFrame) {
+ if (isMainFrame) {
+ mDidFinishLoad = true;
+ trySwappingBackgroundView();
+ }
+ }
+
+ @Override
+ public void didFailLoad(boolean isProvisionalLoad,
+ boolean isMainFrame, int errorCode, String description, String failingUrl) {
+ if (isMainFrame) {
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap",
+ SWAP_REASON_ORIGINAL_FAIL, SWAP_REASON_BOUNDARY);
+ mDidFinishLoad = true;
+ trySwappingBackgroundView();
+ }
+ }
+ };
+
+ mContentViewCore.setContentViewClient(new ContentViewClient() {
+ @Override
+ public void onOffsetsForFullscreenChanged(
+ float topControlsOffsetYPix,
+ float contentOffsetYPix,
+ float overdrawBottomHeightPix) {
+ // Using onOffsetsForFullscreenChanged, as it is called on every frame update.
+ // Here we know that we have an update to frame. Ideally we need a function like
+ // onUpdateFrame.
+ if (mPaintedNonEmpty
+ // We need to make sure the frame we got has some content. Sometimes
+ // didFirstVisuallyNonEmptyPaint is called, but still the frame is empty and
+ // has the default width of Chrome '980'px. So waiting for the contentWidth
+ // of both preview and original to become same. Ideally in all cases they
+ // should be same as they both are same content. Also if we get hung here
+ // for some pages in the worst cases we swap on full load of original, or
+ // our hard timeout will kick in.
+ && getContentViewCore().getContentWidthCss()
+ == mTab.getContentViewCore().getContentWidthCss()
+ && !mSwapInProgress) {
+ mNumRendererFramesReceived++;
+ trySwappingBackgroundView();
+ }
+ }
+ });
+
+ if (mSwapTimeoutMs > 0) mHandler.postDelayed(mSwapOnTimeout, mSwapTimeoutMs);
+
+ nativeSetWebContents(mNativeBackgroundContentViewHelperPtr,
+ mContentViewCore, mWebContentsDelegate);
+
+ mBackgroundLoadStartTimeStampMs = System.currentTimeMillis();
+
+ loadUrl(url);
+ // Keep the toolbar always show in background view.
+ mContentViewCore.getWebContents().updateTopControlsState(false, true, false);
+ mTab.attachOverlayContentViewCore(mContentViewCore, false);
+ }
+
+ /**
+ * Helper to delete {@link ContentViewCore} and it's native web contents object.
+ */
+ private void destroyContentViewCore() {
+ if (mContentViewCore != null && mNativeBackgroundContentViewHelperPtr != 0) {
+ RecordHistogram.recordTimesHistogram(
+ "InstantSearchClicks.TimeInPreview",
+ System.currentTimeMillis() - mBackgroundLoadStartTimeStampMs,
+ TimeUnit.MILLISECONDS);
+ mTab.detachOverlayContentViewCore(mContentViewCore);
+ mContentViewCore.destroy();
+ nativeDestroyWebContents(mNativeBackgroundContentViewHelperPtr);
+ }
+ reset();
+ // Note: We cannot set these in reset(), as we call reset() from releaseContentView and
+ // these values are used after that.
+ mSwapInProgress = false;
+ mLoadProgress = 0;
+ mDidStartLoad = false;
+ mDidFinishLoad = false;
+ }
+
+ /**
+ * @return True iff Background view has finished loading or all the following conditions are
+ * met.
+ * 1. Preview page has loaded completely.
+ * 2. Background view has loaded atleast of preview page height.
+ * 3. Background view has painted anything non-empty.
+ */
+ private boolean isBackgroundViewReady() {
+ if (mDidFinishLoad) return true;
+
+ if (!mPreviewLoaded) return false;
+
+ if (!mPaintedNonEmpty) return false;
+
+ if (mNumRendererFramesReceived < mMinRendererFramesNeededForSwap) return false;
+
+ if (!hasBackgroundPageLoadedMoreThanPreview()) return false;
+
+ return true;
+ }
+
+ /** Called when any of the conditions needed for background view swap are met. */
+ private void trySwappingBackgroundView() {
+ if (mSwapInProgress) return;
+ // This function is asynchronously called from various places.
+ // So make sure we have background content view before doing anything.
+ if (!hasPendingBackgroundPage()) return;
+ if (isBackgroundViewReady()) backgroundViewReady();
+ }
+
+ /** Called when we are ready to swap in background view to foreground. */
+ private void backgroundViewReady() {
+ if (mDelegate == null) return;
+ mSwapInProgress = true;
+ swapInBackgroundViewAfterScroll();
+ }
+
+ private boolean isGooglePreviewServerHost(String host) {
+ if (host == null) return false;
+ return TextUtils.equals(host, GOOGLE_PREVIEW_SERVER_PREFIX + GOOGLE_PREVIEW_SERVER_SUFFIX)
+ || (ChromeVersionInfo.isDevBuild() && host.endsWith(GOOGLE_PREVIEW_SERVER_SUFFIX)
+ && host.contains("promise"));
+ }
+
+ private Uri getUri(String url) {
+ return Uri.parse(url == null ? "" : url);
+ }
+
+ /**
+ * @return original url if the passed in url is a preview url, else null.
+ */
+ private String getOriginalUrlForPreviewUrl(String previewUrl) {
+ Uri previewUri = getUri(previewUrl);
+ String originalUrl = null;
+ if (isGooglePreviewServerHost(previewUri.getHost())) {
+ try {
+ originalUrl = (new URL(previewUri.getQueryParameter("url"))).toString();
+ } catch (MalformedURLException mue) {
+ originalUrl = null;
+ }
+ }
+ return originalUrl;
+ }
+
+
+ /**
+ * Checks if the incoming url is instant search clicks eligible url. Only urls with referer
+ * containing google search domain and 'gcjeid' param are instant search clicks eligible url.
+ * @param url Url string.
+ */
+ private boolean isInstantSearchClicksControlUrl(String url) {
+ String referrer = mTab.getContentViewCore().getWebContents()
+ .getNavigationController().getOriginalUrlForVisibleNavigationEntry();
+ Uri referrerUri = getUri(referrer);
+ Uri previewUri = getUri(url);
+ if (referrerUri.getHost() == null || previewUri.getHost() == null) return false;
+ // Check if the url is coming from search page and has gcjeid param.
+ // TODO(ksimbili): Make this check stricter.
+ return (TextUtils.indexOf(referrerUri.getHost(), GOOGLE_DOMAIN_PREFIX) == 0
+ && referrerUri.getQueryParameter(GWS_CHROME_JOINT_EXPERIMENT_ID_STRING) != null
+ && TextUtils.indexOf(previewUri.getHost(), GOOGLE_DOMAIN_PREFIX) == -1);
+ }
+
+ private void setLogUma() {
+ // We ideally need to listen for gestures on current tab only while swapping to detect when
+ // scrolling ends(if one is active). But for UMA logging, we are listening for it always.
+ mTab.getContentViewCore().addGestureStateListener(mCurrentViewGestureStateListener);
+ mRecordUma = true;
+ }
+
+ private void resetLogUma() {
+ if (mTab.getContentViewCore() != null) {
+ mTab.getContentViewCore().removeGestureStateListener(mCurrentViewGestureStateListener);
+ }
+ mRecordUma = false;
+ }
+
+ /**
+ * Loads the original url in the background view, if the passed in url is a preview url.
+ * @param previewUrl Preview url string.
+ */
+ public void loadOriginalUrlIfPreview(String previewUrl) {
+ if (mExperimentGroup == INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED) {
+ // If feature is disabled return;
+ return;
+ }
+
+ if (mExperimentGroup == INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL
+ && isInstantSearchClicksControlUrl(previewUrl)) {
+ setLogUma();
+ return;
+ }
+
+ String originalUrl = getOriginalUrlForPreviewUrl(previewUrl);
+ if (originalUrl == null) return;
+
+ if (hasPendingBackgroundPage()) {
+ // The following condition is safe, as browser reload will always be on original url.
+ // Hence we will never encounter a situation where user is clicking on 'Reload' button
+ // and the page is not reloading.
+ if (originalUrl.equals(mContentViewCore.getWebContents().getUrl())) return;
+
+ // Log uma for the previous preview getting aborted
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap",
+ SWAP_REASON_ABORT_ON_NAVIGATE, SWAP_REASON_BOUNDARY);
+ }
+
+ // Build content view.
+ createContentViewCoreWithUrl(originalUrl);
+
+ setLogUma();
+ }
+
+ /**
+ * Transfers zoom level from current cvc to the background cvc.
+ * @return Whether zoom level of background cvc had to be changed. It happens only when it is
+ * different from current cvc.
+ */
+ private boolean transferPageScaleFactor() {
+ float currentViewPageScaleFactor =
+ mTab.getContentViewCore().getRenderCoordinates().getPageScaleFactor();
+
+ ContentViewCore backgroundViewCore = getContentViewCore();
+ RenderCoordinates rc = backgroundViewCore.getRenderCoordinates();
+
+ // Clamp the input value.
+ float newPageScaleFactor = Math.max(rc.getMinPageScaleFactor(),
+ Math.min(currentViewPageScaleFactor, rc.getMaxPageScaleFactor()));
+ if (newPageScaleFactor == rc.getPageScaleFactor()) return false;
+
+ float pageScaleFactorDelta = (newPageScaleFactor / rc.getPageScaleFactor());
+ backgroundViewCore.pinchByDelta(pageScaleFactorDelta);
+ mOriginalPageZoomed = true;
+ return true;
+ }
+
+ /**
+ * Transfers scroll offset from current cvc to the background cvc.
+ * @return Whether scroll offset of background cvc had to be changed. It happens only when it
+ * is different from current cvc.
+ */
+ private boolean transferScrollOffset() {
+ ContentViewCore currentViewCore = mTab.getContentViewCore();
+ RenderCoordinates currentViewRenderCoordinates = currentViewCore.getRenderCoordinates();
+ float currentViewScrollX = currentViewRenderCoordinates.getScrollXPix();
+ float currentViewScrollY = currentViewRenderCoordinates.getScrollYPix();
+
+ ContentViewCore backgroundViewCore = getContentViewCore();
+ RenderCoordinates backgroundViewRenderCoordinates =
+ backgroundViewCore.getRenderCoordinates();
+ float maxHorizontalScroll = backgroundViewRenderCoordinates.getMaxHorizontalScrollPix();
+ float maxVerticalScroll = backgroundViewRenderCoordinates.getMaxVerticalScrollPix();
+
+ // Bound the scroll offsets to max scroll values.
+ float newScrollX = Math.min(currentViewScrollX, maxHorizontalScroll);
+ float newScrollY = Math.min(currentViewScrollY, maxVerticalScroll);
+
+ float backgroundViewScrollX = backgroundViewRenderCoordinates.getScrollXPix();
+ float backgroundViewScrollY = backgroundViewRenderCoordinates.getScrollYPix();
+ int scrollByX = Math.round(backgroundViewRenderCoordinates.fromPixToLocalCss(
+ newScrollX - backgroundViewScrollX));
+ int scrollByY = Math.round(backgroundViewRenderCoordinates.fromPixToLocalCss(
+ newScrollY - backgroundViewScrollY));
+
+ if (Math.abs(scrollByX) <= 1 && Math.abs(scrollByY) <= 1) return false;
+
+ if (!mOriginalPageZoomed) {
+ backgroundViewCore.scrollBy(Math.round(newScrollX - backgroundViewScrollX),
+ Math.round(newScrollY - backgroundViewScrollY));
+ } else {
+ backgroundViewCore.scrollTo(Math.round(newScrollX), Math.round(newScrollY));
+ }
+ return true;
+ }
+
+ private void swapInBackgroundViewAfterScroll() {
+ getContentViewCore().addGestureStateListener(mBackgroundGestureStateListener);
+ syncPageStateAndSwapIfReady();
+ }
+
+ // Set scroll offset and zoom values from current cvc and swap the cvc if the both are synced.
+ private void syncPageStateAndSwapIfReady() {
+ // This function is asynchronously called from various places.
+ // So make sure we have background content view before doing anything.
+ if (!hasPendingBackgroundPage() || !isPageSwappingInProgress()) return;
+
+ ContentViewCore currentViewCore = mTab.getContentViewCore();
+ if (currentViewCore == null || currentViewCore.isScrollInProgress()) {
+ // We'll comeback here on scrollEnded and flingEndGesture.
+ return;
+ }
+
+ if (transferPageScaleFactor()) return;
+
+ if (transferScrollOffset()) return;
+
+ RenderCoordinates currentViewRenderCoordinates = currentViewCore.getRenderCoordinates();
+ int currentViewScrollY = currentViewRenderCoordinates.getScrollYPixInt();
+
+ int state;
+ if (currentViewScrollY == 0) {
+ state = PREVIEW_SCROLL_STATE_NO_SCROLL;
+ } else if (currentViewScrollY >= currentViewRenderCoordinates.getMaxVerticalScrollPix()) {
+ state = PREVIEW_SCROLL_STATE_SCROLL_AT_BOTTOM;
+ } else {
+ state = PREVIEW_SCROLL_STATE_SCROLL;
+ }
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.PreviewScrollState", state, PREVIEW_SCROLL_STATE_BOUNDARY);
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap", SWAP_REASON_REGULAR, SWAP_REASON_BOUNDARY);
+
+ // We are ready swap the view now.
+ swapInBackgroundView();
+ }
+
+ private void swapInBackgroundView() {
+ // This function is asynchronously called from various places.
+ // So make sure we have background content view before doing anything.
+ if (!hasPendingBackgroundPage()) return;
+
+ if (mTab.getContentViewCore() != null) {
+ mTab.getContentViewCore().removeGestureStateListener(mCurrentViewGestureStateListener);
+ }
+ getContentViewCore().removeGestureStateListener(mBackgroundGestureStateListener);
+ assert mDelegate != null;
+
+ // Make sure the ContentViewCore is visible.
+ getContentViewCore().setDrawsContent(true);
+
+ // Merge history stack before swap.
+ nativeMergeHistoryFrom(mNativeBackgroundContentViewHelperPtr, mTab.getWebContents());
+ ContentViewCore cvc = releaseContentViewCore();
+ mDelegate.onBackgroundViewReady(cvc, mDidStartLoad, mDidFinishLoad, mLoadProgress);
+
+ RecordHistogram.recordTimesHistogram("InstantSearchClicks.TimeToSwap",
+ System.currentTimeMillis() - mBackgroundLoadStartTimeStampMs,
+ TimeUnit.MILLISECONDS);
+
+ // Content view is released already, but still calling this to clear some state.
+ destroyContentViewCore();
+
+ // We need to include metrics from orignal page after swap too. For example, single tap
+ // metric should include taps in both preview and original page after swap. This we reset
+ // when tab commits a new navigation.
+ setLogUma();
+ }
+
+ /** Stop the current navigation. */
+ public void stopLoading() {
+ destroyContentViewCore();
+ }
+
+ /**
+ * Forces swapping of content views.
+ */
+ public void forceSwappingContentViews() {
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap", SWAP_REASON_FORCE, SWAP_REASON_BOUNDARY);
+ swapInBackgroundView();
+ }
+
+ /**
+ * Calls 'unload' handler and deletes the webContents.
+ * @param webContents The webcontents to be deleted.
+ */
+ public void unloadAndDeleteWebContents(WebContents webContents) {
+ nativeUnloadAndDeleteWebContents(mNativeBackgroundContentViewHelperPtr, webContents);
+ }
+
+ /**
+ * Helper to reset the state of the object.
+ */
+ private void reset() {
+ mContentViewCore = null;
+ if (mWebContentsObserver != null) {
+ mWebContentsObserver.destroy();
+ mWebContentsObserver = null;
+ }
+
+ mPreviewLoaded = false;
+ mPaintedNonEmpty = false;
+ mNumRendererFramesReceived = 0;
+ resetLogUma();
+ mHandler.removeCallbacks(mSwapOnTimeout);
+ mOriginalPageZoomed = false;
+ }
+
+ @Override
+ public void onLoadProgressChanged(Tab tab, int progress) {
+ if (!hasPendingBackgroundPage()) return;
+ if (progress >= 100) {
+ mPreviewLoaded = true;
+ trySwappingBackgroundView();
+ }
+ }
+
+ @Override
+ public void onContentChanged(Tab tab) {
+ if (mCurrentViewWebContentsObserver != null) {
+ mCurrentViewWebContentsObserver.destroy();
+ }
+ if (tab.getContentViewCore() == null) return;
+
+ loadOriginalUrlIfPreview(mTab.getUrl());
+
+ mCurrentViewWebContentsObserver = new WebContentsObserver(mTab.getWebContents()) {
+ @Override
+ public void didFailLoad(boolean isProvisionalLoad,
+ boolean isMainFrame, int errorCode, String description, String failingUrl) {
+ if (isMainFrame && hasPendingBackgroundPage()) {
+ if (TextUtils.equals(mContentViewCore.getWebContents().getUrl(),
+ getOriginalUrlForPreviewUrl(failingUrl))) {
+ forceSwappingContentViews();
+ RecordHistogram.recordEnumeratedHistogram(
+ "InstantSearchClicks.ReasonForSwap",
+ SWAP_REASON_ON_PREVIEW_FAIL, SWAP_REASON_BOUNDARY);
+ }
+ }
+ }
+
+ @Override
+ public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId,
+ boolean isMainFrame, String validatedUrl, boolean isErrorPage,
+ boolean isIframeSrcdoc) {
+ if (isMainFrame) {
+ if (mTab.getContentViewCore() != null) {
+ loadOriginalUrlIfPreview(validatedUrl);
+ } else {
+ destroyContentViewCore();
+ }
+ }
+ }
+
+ @Override
+ public void didCommitProvisionalLoadForFrame(
+ long frameId, boolean isMainFrame, String url, int transitionType) {
+ if (!isMainFrame) return;
+
+ if (!hasPendingBackgroundPage() && !isInstantSearchClicksControlUrl(url)) {
+ resetLogUma();
+ }
+
+ if (hasPendingBackgroundPage() && getOriginalUrlForPreviewUrl(url) == null) {
+ // We don't destroy the contentView in onDidStartProvisionalLoadForFrame, if the
+ // url is navigating to non-preview URL. We instead do that here. Some urls
+ // which are handled using intents, the url is never commited. In which case we
+ // still want to swap to happen.
+ destroyContentViewCore();
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onDestroyed(Tab tab) {
+ destroy();
+ }
+
+ @CalledByNative
+ private long getNativePtr() {
+ return mNativeBackgroundContentViewHelperPtr;
+ }
+
+ public void recordBack() {
+ if (mRecordUma) RecordUserAction.record("MobilePreviewPageBack");
+ }
+
+ public void recordReload() {
+ if (mRecordUma) RecordUserAction.record("MobilePreviewPageReload");
+ }
+
+ public void recordTabClose() {
+ if (mRecordUma) RecordUserAction.record("MobilePreviewPageTabClose");
+ }
+
+ public void recordSingleTap() {
+ if (mRecordUma) RecordUserAction.record("MobilePreviewPageSingleTap");
+ }
+
+ private native long nativeInit();
+ private native void nativeDestroy(long nativeBackgroundContentViewHelper);
+ private native void nativeSetWebContents(long nativeBackgroundContentViewHelper,
+ ContentViewCore contentViewCore, WebContentsDelegateAndroid delegate);
+ private native void nativeDestroyWebContents(long nativeBackgroundContentViewHelper);
+ private native void nativeReleaseWebContents(long nativeBackgroundContentViewHelper);
+ private native void nativeMergeHistoryFrom(long nativeBackgroundContentViewHelper,
+ WebContents webContents);
+ private native void nativeUnloadAndDeleteWebContents(long nativeBackgroundContentViewHelper,
+ WebContents webContents);
+ private native int nativeGetSwapTimeoutMs(long nativeBackgroundContentViewHelper);
+ private native int nativeGetNumFramesNeededForSwap(long nativeBackgroundContentViewHelper);
+}

Powered by Google App Engine
This is Rietveld 408576698