OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 package org.chromium.chrome.browser.tab; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.net.Uri; |
| 9 import android.os.Handler; |
| 10 import android.text.TextUtils; |
| 11 |
| 12 import org.chromium.base.CalledByNative; |
| 13 import org.chromium.base.FieldTrialList; |
| 14 import org.chromium.base.metrics.RecordHistogram; |
| 15 import org.chromium.base.metrics.RecordUserAction; |
| 16 import org.chromium.chrome.browser.ChromeVersionInfo; |
| 17 import org.chromium.chrome.browser.ContentViewUtil; |
| 18 import org.chromium.chrome.browser.EmptyTabObserver; |
| 19 import org.chromium.chrome.browser.Tab; |
| 20 import org.chromium.chrome.browser.device.DeviceClassManager; |
| 21 import org.chromium.components.web_contents_delegate_android.WebContentsDelegate
Android; |
| 22 import org.chromium.content.browser.ContentView; |
| 23 import org.chromium.content.browser.ContentViewClient; |
| 24 import org.chromium.content.browser.ContentViewCore; |
| 25 import org.chromium.content.browser.RenderCoordinates; |
| 26 import org.chromium.content_public.browser.GestureStateListener; |
| 27 import org.chromium.content_public.browser.LoadUrlParams; |
| 28 import org.chromium.content_public.browser.WebContents; |
| 29 import org.chromium.content_public.browser.WebContentsObserver; |
| 30 import org.chromium.content_public.common.Referrer; |
| 31 import org.chromium.ui.base.WindowAndroid; |
| 32 |
| 33 import java.net.MalformedURLException; |
| 34 import java.net.URL; |
| 35 import java.util.concurrent.TimeUnit; |
| 36 |
| 37 /** |
| 38 * Responsible for background content view creation and management. |
| 39 */ |
| 40 public class BackgroundContentViewHelper extends EmptyTabObserver { |
| 41 private static final String GOOGLE_PREVIEW_SERVER_PREFIX = "icl"; |
| 42 |
| 43 // Google preview staging and canary servers. Only to be used in dev builds. |
| 44 private static final String GOOGLE_PREVIEW_SERVER_SUFFIX = ".googleuserconte
nt.com"; |
| 45 |
| 46 private static final String GWS_CHROME_JOINT_EXPERIMENT_ID_STRING = "gcjeid"
; |
| 47 |
| 48 private static final String GOOGLE_DOMAIN_PREFIX = "www.google."; |
| 49 |
| 50 // Instant search clicks swap reasons. |
| 51 // IMPORTANT: Do not renumber any existing constants (except the BOUNDARY on
es). Keep these in |
| 52 // sync with histograms.xml file. |
| 53 private static final int SWAP_REASON_REGULAR = 0; |
| 54 private static final int SWAP_REASON_TIMEOUT = 1; |
| 55 private static final int SWAP_REASON_ABORT_ON_NAVIGATE = 2; |
| 56 private static final int SWAP_REASON_ON_PREVIEW_FAIL = 3; |
| 57 private static final int SWAP_REASON_ORIGINAL_FAIL = 4; |
| 58 private static final int SWAP_REASON_FORCE = 5; |
| 59 private static final int SWAP_REASON_BOUNDARY = 6; |
| 60 |
| 61 // Instant search clicks scroll state while swapping. |
| 62 // IMPORTANT: Do not renumber any existing constants (except the BOUNDARY on
es). Keep these in |
| 63 // sync with histograms.xml file. |
| 64 private static final int PREVIEW_SCROLL_STATE_NO_SCROLL = 0; |
| 65 private static final int PREVIEW_SCROLL_STATE_SCROLL = 1; |
| 66 private static final int PREVIEW_SCROLL_STATE_SCROLL_AT_BOTTOM = 2; |
| 67 private static final int PREVIEW_SCROLL_STATE_BOUNDARY = 3; |
| 68 |
| 69 // Instant search clicks experiment state. |
| 70 private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED = 0; |
| 71 private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_ENABLED = 1; |
| 72 private static final int INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL = 2; |
| 73 |
| 74 /** |
| 75 * The maximum amount of time to wait for the background page to swap. We fo
rce the swap after |
| 76 * this timeout. |
| 77 */ |
| 78 private final int mSwapTimeoutMs; |
| 79 |
| 80 /** |
| 81 * The minimum number of renderer frames needed before swap is triggered. |
| 82 */ |
| 83 private final int mMinRendererFramesNeededForSwap; |
| 84 |
| 85 // Native pointer corresponding to the current object. |
| 86 private long mNativeBackgroundContentViewHelperPtr; |
| 87 |
| 88 // The content view core created to load url in background. |
| 89 private ContentViewCore mContentViewCore; |
| 90 |
| 91 // Android window used to created content view. |
| 92 private final WindowAndroid mWindowAndroid; |
| 93 |
| 94 // The webcontents observer on current content view of the tab. |
| 95 private WebContentsObserver mCurrentViewWebContentsObserver; |
| 96 |
| 97 // The webcontents observer that listens for events to swap in the new WebCo
ntents. |
| 98 private WebContentsObserver mWebContentsObserver; |
| 99 |
| 100 // The webcontents delegate that gets notified when background view is ready
to be swapped. |
| 101 private final WebContentsDelegateAndroid mWebContentsDelegate; |
| 102 |
| 103 // Whether or not the preview page finished loading. |
| 104 private boolean mPreviewLoaded = false; |
| 105 |
| 106 // Whether or not the background view has painted anything non-empty. |
| 107 private boolean mPaintedNonEmpty = false; |
| 108 |
| 109 private int mNumRendererFramesReceived = 0; |
| 110 |
| 111 // Whether or not the swap is in progress. We will be in this state while sy
ncing scroll offsets |
| 112 // between content views. |
| 113 private boolean mSwapInProgress = false; |
| 114 |
| 115 // Whether or not the background view started loading content. |
| 116 private boolean mDidStartLoad = false; |
| 117 |
| 118 // Whether or not the background view finished loading. |
| 119 private boolean mDidFinishLoad = false; |
| 120 |
| 121 // The tab associated with the background view. |
| 122 private final Tab mTab; |
| 123 |
| 124 // The delegate who is notified when background content view is ready. |
| 125 private final BackgroundContentViewDelegate mDelegate; |
| 126 |
| 127 // GestureStateListener attached to the background contentView. |
| 128 private final GestureStateListener mBackgroundGestureStateListener; |
| 129 |
| 130 // GestureStateListener attached to the current contentView of tab. |
| 131 private final GestureStateListener mCurrentViewGestureStateListener; |
| 132 |
| 133 private final Handler mHandler; |
| 134 |
| 135 private final Runnable mSwapOnTimeout = new Runnable() { |
| 136 @Override |
| 137 public void run() { |
| 138 forceSwappingContentViews(); |
| 139 RecordHistogram.recordEnumeratedHistogram( |
| 140 "InstantSearchClicks.ReasonForSwap", |
| 141 SWAP_REASON_TIMEOUT, SWAP_REASON_BOUNDARY); |
| 142 } |
| 143 }; |
| 144 |
| 145 private long mBackgroundLoadStartTimeStampMs; |
| 146 |
| 147 /** |
| 148 * Whether or not to record 'instant search clicks' uma for the current load
ed page in tab. |
| 149 * This is set to true, for instant search clicks enabled urls(preview urls)
and also for the |
| 150 * 'would have been' instant search click enabled urls. |
| 151 */ |
| 152 private boolean mRecordUma = false; |
| 153 |
| 154 private final int mExperimentGroup; |
| 155 |
| 156 /** |
| 157 * The recent load progress before swapping. This will be used to return the
progress after |
| 158 * swapping the page views. |
| 159 */ |
| 160 private int mLoadProgress; |
| 161 |
| 162 /** |
| 163 * Whether we transferred pagescale from preview to original. |
| 164 */ |
| 165 private boolean mOriginalPageZoomed = false; |
| 166 |
| 167 /** |
| 168 * The delegate that is notified when BackgroundContentView is ready to be s
wapped in. |
| 169 */ |
| 170 public interface BackgroundContentViewDelegate { |
| 171 /** |
| 172 * Called when the background content view has drawn non-empty screen.. |
| 173 * @param cvc The background ContentViewCore that is ready to be swapped
in. |
| 174 * @param didStartLoad Whether WebContentsObserver::DidStartProvisional
LoadForFrame() has |
| 175 * been called for background ContentViewCore. |
| 176 * @param didFinishLoad Whether WebContentsObserver::DidFinishLoad() has
already been |
| 177 * called for background {@link ContentViewCore}. |
| 178 * @param progress The recent load progress that was received for t
he |
| 179 * {@link ContentView}. |
| 180 */ |
| 181 void onBackgroundViewReady( |
| 182 ContentViewCore cvc, boolean didStartLoad, boolean didFinishLoad
, int progress); |
| 183 |
| 184 /** |
| 185 * Called to notify the load progress of backgrounc content view. |
| 186 * @param progress The recent load progress that was received for t
he |
| 187 * {@link ContentView}. |
| 188 */ |
| 189 void onLoadProgressChanged(int progress); |
| 190 } |
| 191 |
| 192 /** |
| 193 * Creates an instance of BackgroundContentViewHelper. |
| 194 * @param window An instance of a {@link WindowAndroid}. |
| 195 * @param tab The {@link ChromeTab} associated with current backgruond
view. |
| 196 * @param delegate The {@link BackgroundContentViewDelegate} to notify when
backgruond view is |
| 197 * ready to swap in. |
| 198 */ |
| 199 public BackgroundContentViewHelper(WindowAndroid windowAndroid, Tab tab, |
| 200 BackgroundContentViewDelegate delegate) { |
| 201 mWindowAndroid = windowAndroid; |
| 202 mNativeBackgroundContentViewHelperPtr = nativeInit(); |
| 203 |
| 204 mTab = tab; |
| 205 assert delegate != null; |
| 206 mDelegate = delegate; |
| 207 mTab.addObserver(this); |
| 208 mBackgroundGestureStateListener = new GestureStateListener() { |
| 209 @Override |
| 210 public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scr
ollExtentY) { |
| 211 syncPageStateAndSwapIfReady(); |
| 212 } |
| 213 }; |
| 214 |
| 215 mCurrentViewGestureStateListener = new GestureStateListener() { |
| 216 @Override |
| 217 public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY)
{ |
| 218 syncPageStateAndSwapIfReady(); |
| 219 } |
| 220 |
| 221 @Override |
| 222 public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { |
| 223 syncPageStateAndSwapIfReady(); |
| 224 } |
| 225 |
| 226 @Override |
| 227 public void onSingleTap(boolean consumed, int x, int y) { |
| 228 recordSingleTap(); |
| 229 } |
| 230 }; |
| 231 |
| 232 mWebContentsDelegate = new WebContentsDelegateAndroid() { |
| 233 @Override |
| 234 public void onLoadProgressChanged(int progress) { |
| 235 mLoadProgress = progress; |
| 236 mDelegate.onLoadProgressChanged(progress); |
| 237 trySwappingBackgroundView(); |
| 238 } |
| 239 }; |
| 240 mHandler = new Handler(); |
| 241 |
| 242 mSwapTimeoutMs = nativeGetSwapTimeoutMs(mNativeBackgroundContentViewHelp
erPtr); |
| 243 mMinRendererFramesNeededForSwap = |
| 244 nativeGetNumFramesNeededForSwap(mNativeBackgroundContentViewHelperPt
r); |
| 245 mBackgroundLoadStartTimeStampMs = System.currentTimeMillis(); |
| 246 mExperimentGroup = getExperimentGroup(); |
| 247 } |
| 248 |
| 249 /* |
| 250 * @return Experiment group that the current user fall into. |
| 251 */ |
| 252 private int getExperimentGroup() { |
| 253 if (!DeviceClassManager.enableInstantSearchClicks()) { |
| 254 return INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED; |
| 255 } |
| 256 |
| 257 String instantSearchClicksFieldTrialValue = |
| 258 FieldTrialList.findFullName("InstantSearchClicks"); |
| 259 if (TextUtils.equals(instantSearchClicksFieldTrialValue, "Enabled") |
| 260 || TextUtils.equals(instantSearchClicksFieldTrialValue, |
| 261 "InstantSearchClicksEnabled")) { |
| 262 return INSTANT_SEARCH_CLICKS_EXPERIMENT_ENABLED; |
| 263 } else if (TextUtils.equals(instantSearchClicksFieldTrialValue, "Control
")) { |
| 264 return INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL; |
| 265 } |
| 266 return INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED; |
| 267 } |
| 268 |
| 269 /** |
| 270 * @return True iff content height of original page is greater than preview
page. |
| 271 */ |
| 272 private boolean hasBackgroundPageLoadedMoreThanPreview() { |
| 273 if (!mPaintedNonEmpty || mTab.getContentViewCore() == null) return false
; |
| 274 return getContentViewCore().getContentHeightCss() |
| 275 > mTab.getContentViewCore().getContentHeightCss(); |
| 276 } |
| 277 |
| 278 /** |
| 279 * @return True iff original page is loading in background content view whic
h is not swapped in. |
| 280 */ |
| 281 public boolean hasPendingBackgroundPage() { |
| 282 return mContentViewCore != null; |
| 283 } |
| 284 |
| 285 /** |
| 286 * @return True iff swapping is in progress. This is true till the content v
iew is completely |
| 287 * swapped in. |
| 288 */ |
| 289 public boolean isPageSwappingInProgress() { |
| 290 return mSwapInProgress; |
| 291 } |
| 292 |
| 293 /** |
| 294 * @return The {@link ContentViewCore} associated with the current backgroun
d page. |
| 295 */ |
| 296 public ContentViewCore getContentViewCore() { |
| 297 return mContentViewCore; |
| 298 } |
| 299 |
| 300 /** @return The recent progress of background content view. */ |
| 301 public int getProgress() { |
| 302 return mLoadProgress; |
| 303 } |
| 304 |
| 305 /** |
| 306 * Loads the specified URL in the background content view. |
| 307 * @param url the url to load. |
| 308 */ |
| 309 private void loadUrl(String url) { |
| 310 if (mNativeBackgroundContentViewHelperPtr == 0) return; |
| 311 |
| 312 if (mContentViewCore == null) return; |
| 313 |
| 314 mContentViewCore.onShow(); |
| 315 |
| 316 LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| 317 // Use the referrer of preview url as referrer for original url. |
| 318 // We are always loading url during onDidStartProvisionalLoadForFrame of
preview URL, where |
| 319 // load is not yet committed and so is the navigation entry. The current
active |
| 320 // navigation entry is still the referrer for preview url. |
| 321 String referrerString = mTab.getWebContents() |
| 322 .getNavigationController().getOriginalUrlForVisibleNavigationEnt
ry(); |
| 323 Uri referrer = getUri(referrerString); |
| 324 String referrerHost = referrer.getHost(); |
| 325 if (referrerHost == null |
| 326 || TextUtils.indexOf(referrerHost, GOOGLE_DOMAIN_PREFIX) != 0) { |
| 327 referrerHost = "www.google.com"; |
| 328 } |
| 329 // We come here only through google.com and google.com always sends the
referrer with |
| 330 // value at least equal to the origin (and full url in http requests). |
| 331 // Add 'gcjeid' param to referrer so that the PLT metrics are logged in
a different bucket. |
| 332 // PLT metrics are logged in a different bucket if the referer contains
'gcjeid' param. |
| 333 // For Experiment: gcjeid=19 |
| 334 String referrerTrimmed = "http://" + referrerHost + "/?" |
| 335 + GWS_CHROME_JOINT_EXPERIMENT_ID_STRING + "=19"; |
| 336 loadUrlParams.setReferrer(new Referrer(referrerTrimmed, 0 /* WebReferrer
PolicyAlways */)); |
| 337 mContentViewCore.getWebContents().getNavigationController().loadUrl(load
UrlParams); |
| 338 } |
| 339 |
| 340 /** |
| 341 * Releases the ownership of the content view created. Also, resets the whol
e state of the |
| 342 * current object. |
| 343 */ |
| 344 private ContentViewCore releaseContentViewCore() { |
| 345 mTab.detachOverlayContentViewCore(mContentViewCore); |
| 346 |
| 347 ContentViewCore originalContentViewCore = mContentViewCore; |
| 348 if (mContentViewCore != null && mNativeBackgroundContentViewHelperPtr !=
0) { |
| 349 nativeReleaseWebContents(mNativeBackgroundContentViewHelperPtr); |
| 350 } |
| 351 reset(); |
| 352 return originalContentViewCore; |
| 353 } |
| 354 |
| 355 /** |
| 356 * Destroys the contentview and the corresponding native object. |
| 357 */ |
| 358 private void destroy() { |
| 359 if (mCurrentViewWebContentsObserver != null) { |
| 360 mCurrentViewWebContentsObserver.destroy(); |
| 361 mCurrentViewWebContentsObserver = null; |
| 362 } |
| 363 destroyContentViewCore(); |
| 364 |
| 365 if (mNativeBackgroundContentViewHelperPtr == 0) return; |
| 366 |
| 367 nativeDestroy(mNativeBackgroundContentViewHelperPtr); |
| 368 mNativeBackgroundContentViewHelperPtr = 0; |
| 369 } |
| 370 |
| 371 /** |
| 372 * Creates a new {@link ContentViewCore} and loads the given url. |
| 373 * @param url Url to load. |
| 374 */ |
| 375 private void createContentViewCoreWithUrl(String url) { |
| 376 if (url == null || mNativeBackgroundContentViewHelperPtr == 0) return; |
| 377 |
| 378 destroyContentViewCore(); |
| 379 |
| 380 Activity activity = mWindowAndroid.getActivity().get(); |
| 381 mContentViewCore = new ContentViewCore(activity); |
| 382 ContentView cv = new ContentView(activity, mContentViewCore); |
| 383 mContentViewCore.initialize(cv, cv, |
| 384 ContentViewUtil.createWebContents(mTab.isIncognito(), false), mW
indowAndroid); |
| 385 |
| 386 // The renderer should be set with the height considering the top contro
ls(non-fullscreen |
| 387 // mode). |
| 388 mContentViewCore.setTopControlsHeight(mContentViewCore.getTopControlsHei
ghtPix(), true); |
| 389 mWebContentsObserver = new WebContentsObserver(mContentViewCore.getWebCo
ntents()) { |
| 390 @Override |
| 391 public void didFirstVisuallyNonEmptyPaint() { |
| 392 mPaintedNonEmpty = true; |
| 393 trySwappingBackgroundView(); |
| 394 } |
| 395 @Override |
| 396 public void didStartProvisionalLoadForFrame(long frameId, long paren
tFrameId, |
| 397 boolean isMainFrame, String validatedUrl, boolean isErrorPag
e, |
| 398 boolean isIframeSrcdoc) { |
| 399 if (isMainFrame) mDidStartLoad = true; |
| 400 } |
| 401 |
| 402 @Override |
| 403 public void didFinishLoad(long frameId, String validatedUrl, boolean
isMainFrame) { |
| 404 if (isMainFrame) { |
| 405 mDidFinishLoad = true; |
| 406 trySwappingBackgroundView(); |
| 407 } |
| 408 } |
| 409 |
| 410 @Override |
| 411 public void didFailLoad(boolean isProvisionalLoad, |
| 412 boolean isMainFrame, int errorCode, String description, Stri
ng failingUrl) { |
| 413 if (isMainFrame) { |
| 414 RecordHistogram.recordEnumeratedHistogram( |
| 415 "InstantSearchClicks.ReasonForSwap", |
| 416 SWAP_REASON_ORIGINAL_FAIL, SWAP_REASON_BOUNDARY); |
| 417 mDidFinishLoad = true; |
| 418 trySwappingBackgroundView(); |
| 419 } |
| 420 } |
| 421 }; |
| 422 |
| 423 mContentViewCore.setContentViewClient(new ContentViewClient() { |
| 424 @Override |
| 425 public void onOffsetsForFullscreenChanged( |
| 426 float topControlsOffsetYPix, |
| 427 float contentOffsetYPix, |
| 428 float overdrawBottomHeightPix) { |
| 429 // Using onOffsetsForFullscreenChanged, as it is called on every
frame update. |
| 430 // Here we know that we have an update to frame. Ideally we need
a function like |
| 431 // onUpdateFrame. |
| 432 if (mPaintedNonEmpty |
| 433 // We need to make sure the frame we got has some conten
t. Sometimes |
| 434 // didFirstVisuallyNonEmptyPaint is called, but still th
e frame is empty and |
| 435 // has the default width of Chrome '980'px. So waiting f
or the contentWidth |
| 436 // of both preview and original to become same. Ideally
in all cases they |
| 437 // should be same as they both are same content. Also if
we get hung here |
| 438 // for some pages in the worst cases we swap on full loa
d of original, or |
| 439 // our hard timeout will kick in. |
| 440 && getContentViewCore().getContentWidthCss() |
| 441 == mTab.getContentViewCore().getContentWidthCss(
) |
| 442 && !mSwapInProgress) { |
| 443 mNumRendererFramesReceived++; |
| 444 trySwappingBackgroundView(); |
| 445 } |
| 446 } |
| 447 }); |
| 448 |
| 449 if (mSwapTimeoutMs > 0) mHandler.postDelayed(mSwapOnTimeout, mSwapTimeou
tMs); |
| 450 |
| 451 nativeSetWebContents(mNativeBackgroundContentViewHelperPtr, |
| 452 mContentViewCore, mWebContentsDelegate); |
| 453 |
| 454 mBackgroundLoadStartTimeStampMs = System.currentTimeMillis(); |
| 455 |
| 456 loadUrl(url); |
| 457 // Keep the toolbar always show in background view. |
| 458 mContentViewCore.getWebContents().updateTopControlsState(false, true, fa
lse); |
| 459 mTab.attachOverlayContentViewCore(mContentViewCore, false); |
| 460 } |
| 461 |
| 462 /** |
| 463 * Helper to delete {@link ContentViewCore} and it's native web contents obj
ect. |
| 464 */ |
| 465 private void destroyContentViewCore() { |
| 466 if (mContentViewCore != null && mNativeBackgroundContentViewHelperPtr !=
0) { |
| 467 RecordHistogram.recordTimesHistogram( |
| 468 "InstantSearchClicks.TimeInPreview", |
| 469 System.currentTimeMillis() - mBackgroundLoadStartTimeStampMs
, |
| 470 TimeUnit.MILLISECONDS); |
| 471 mTab.detachOverlayContentViewCore(mContentViewCore); |
| 472 mContentViewCore.destroy(); |
| 473 nativeDestroyWebContents(mNativeBackgroundContentViewHelperPtr); |
| 474 } |
| 475 reset(); |
| 476 // Note: We cannot set these in reset(), as we call reset() from release
ContentView and |
| 477 // these values are used after that. |
| 478 mSwapInProgress = false; |
| 479 mLoadProgress = 0; |
| 480 mDidStartLoad = false; |
| 481 mDidFinishLoad = false; |
| 482 } |
| 483 |
| 484 /** |
| 485 * @return True iff Background view has finished loading or all the followin
g conditions are |
| 486 * met. |
| 487 * 1. Preview page has loaded completely. |
| 488 * 2. Background view has loaded atleast of preview page height. |
| 489 * 3. Background view has painted anything non-empty. |
| 490 */ |
| 491 private boolean isBackgroundViewReady() { |
| 492 if (mDidFinishLoad) return true; |
| 493 |
| 494 if (!mPreviewLoaded) return false; |
| 495 |
| 496 if (!mPaintedNonEmpty) return false; |
| 497 |
| 498 if (mNumRendererFramesReceived < mMinRendererFramesNeededForSwap) return
false; |
| 499 |
| 500 if (!hasBackgroundPageLoadedMoreThanPreview()) return false; |
| 501 |
| 502 return true; |
| 503 } |
| 504 |
| 505 /** Called when any of the conditions needed for background view swap are me
t. */ |
| 506 private void trySwappingBackgroundView() { |
| 507 if (mSwapInProgress) return; |
| 508 // This function is asynchronously called from various places. |
| 509 // So make sure we have background content view before doing anything. |
| 510 if (!hasPendingBackgroundPage()) return; |
| 511 if (isBackgroundViewReady()) backgroundViewReady(); |
| 512 } |
| 513 |
| 514 /** Called when we are ready to swap in background view to foreground. */ |
| 515 private void backgroundViewReady() { |
| 516 if (mDelegate == null) return; |
| 517 mSwapInProgress = true; |
| 518 swapInBackgroundViewAfterScroll(); |
| 519 } |
| 520 |
| 521 private boolean isGooglePreviewServerHost(String host) { |
| 522 if (host == null) return false; |
| 523 return TextUtils.equals(host, GOOGLE_PREVIEW_SERVER_PREFIX + GOOGLE_PREV
IEW_SERVER_SUFFIX) |
| 524 || (ChromeVersionInfo.isDevBuild() && host.endsWith(GOOGLE_PREVI
EW_SERVER_SUFFIX) |
| 525 && host.contains("promise")); |
| 526 } |
| 527 |
| 528 private Uri getUri(String url) { |
| 529 return Uri.parse(url == null ? "" : url); |
| 530 } |
| 531 |
| 532 /** |
| 533 * @return original url if the passed in url is a preview url, else null. |
| 534 */ |
| 535 private String getOriginalUrlForPreviewUrl(String previewUrl) { |
| 536 Uri previewUri = getUri(previewUrl); |
| 537 String originalUrl = null; |
| 538 if (isGooglePreviewServerHost(previewUri.getHost())) { |
| 539 try { |
| 540 originalUrl = (new URL(previewUri.getQueryParameter("url"))).toS
tring(); |
| 541 } catch (MalformedURLException mue) { |
| 542 originalUrl = null; |
| 543 } |
| 544 } |
| 545 return originalUrl; |
| 546 } |
| 547 |
| 548 |
| 549 /** |
| 550 * Checks if the incoming url is instant search clicks eligible url. Only ur
ls with referer |
| 551 * containing google search domain and 'gcjeid' param are instant search cli
cks eligible url. |
| 552 * @param url Url string. |
| 553 */ |
| 554 private boolean isInstantSearchClicksControlUrl(String url) { |
| 555 String referrer = mTab.getContentViewCore().getWebContents() |
| 556 .getNavigationController().getOriginalUrlForVisibleNavigationEnt
ry(); |
| 557 Uri referrerUri = getUri(referrer); |
| 558 Uri previewUri = getUri(url); |
| 559 if (referrerUri.getHost() == null || previewUri.getHost() == null) retur
n false; |
| 560 // Check if the url is coming from search page and has gcjeid param. |
| 561 // TODO(ksimbili): Make this check stricter. |
| 562 return (TextUtils.indexOf(referrerUri.getHost(), GOOGLE_DOMAIN_PREFIX) =
= 0 |
| 563 && referrerUri.getQueryParameter(GWS_CHROME_JOINT_EXPERIMENT_ID_
STRING) != null |
| 564 && TextUtils.indexOf(previewUri.getHost(), GOOGLE_DOMAIN_PREFIX)
== -1); |
| 565 } |
| 566 |
| 567 private void setLogUma() { |
| 568 // We ideally need to listen for gestures on current tab only while swap
ping to detect when |
| 569 // scrolling ends(if one is active). But for UMA logging, we are listeni
ng for it always. |
| 570 mTab.getContentViewCore().addGestureStateListener(mCurrentViewGestureSta
teListener); |
| 571 mRecordUma = true; |
| 572 } |
| 573 |
| 574 private void resetLogUma() { |
| 575 if (mTab.getContentViewCore() != null) { |
| 576 mTab.getContentViewCore().removeGestureStateListener(mCurrentViewGes
tureStateListener); |
| 577 } |
| 578 mRecordUma = false; |
| 579 } |
| 580 |
| 581 /** |
| 582 * Loads the original url in the background view, if the passed in url is a
preview url. |
| 583 * @param previewUrl Preview url string. |
| 584 */ |
| 585 public void loadOriginalUrlIfPreview(String previewUrl) { |
| 586 if (mExperimentGroup == INSTANT_SEARCH_CLICKS_EXPERIMENT_DISABLED) { |
| 587 // If feature is disabled return; |
| 588 return; |
| 589 } |
| 590 |
| 591 if (mExperimentGroup == INSTANT_SEARCH_CLICKS_EXPERIMENT_CONTROL |
| 592 && isInstantSearchClicksControlUrl(previewUrl)) { |
| 593 setLogUma(); |
| 594 return; |
| 595 } |
| 596 |
| 597 String originalUrl = getOriginalUrlForPreviewUrl(previewUrl); |
| 598 if (originalUrl == null) return; |
| 599 |
| 600 if (hasPendingBackgroundPage()) { |
| 601 // The following condition is safe, as browser reload will always be
on original url. |
| 602 // Hence we will never encounter a situation where user is clicking
on 'Reload' button |
| 603 // and the page is not reloading. |
| 604 if (originalUrl.equals(mContentViewCore.getWebContents().getUrl()))
return; |
| 605 |
| 606 // Log uma for the previous preview getting aborted |
| 607 RecordHistogram.recordEnumeratedHistogram( |
| 608 "InstantSearchClicks.ReasonForSwap", |
| 609 SWAP_REASON_ABORT_ON_NAVIGATE, SWAP_REASON_BOUNDARY); |
| 610 } |
| 611 |
| 612 // Build content view. |
| 613 createContentViewCoreWithUrl(originalUrl); |
| 614 |
| 615 setLogUma(); |
| 616 } |
| 617 |
| 618 /** |
| 619 * Transfers zoom level from current cvc to the background cvc. |
| 620 * @return Whether zoom level of background cvc had to be changed. It happen
s only when it is |
| 621 * different from current cvc. |
| 622 */ |
| 623 private boolean transferPageScaleFactor() { |
| 624 float currentViewPageScaleFactor = |
| 625 mTab.getContentViewCore().getRenderCoordinates().getPageScaleFac
tor(); |
| 626 |
| 627 ContentViewCore backgroundViewCore = getContentViewCore(); |
| 628 RenderCoordinates rc = backgroundViewCore.getRenderCoordinates(); |
| 629 |
| 630 // Clamp the input value. |
| 631 float newPageScaleFactor = Math.max(rc.getMinPageScaleFactor(), |
| 632 Math.min(currentViewPageScaleFactor, rc.getMaxPageScaleFactor())
); |
| 633 if (newPageScaleFactor == rc.getPageScaleFactor()) return false; |
| 634 |
| 635 float pageScaleFactorDelta = (newPageScaleFactor / rc.getPageScaleFactor
()); |
| 636 backgroundViewCore.pinchByDelta(pageScaleFactorDelta); |
| 637 mOriginalPageZoomed = true; |
| 638 return true; |
| 639 } |
| 640 |
| 641 /** |
| 642 * Transfers scroll offset from current cvc to the background cvc. |
| 643 * @return Whether scroll offset of background cvc had to be changed. It hap
pens only when it |
| 644 * is different from current cvc. |
| 645 */ |
| 646 private boolean transferScrollOffset() { |
| 647 ContentViewCore currentViewCore = mTab.getContentViewCore(); |
| 648 RenderCoordinates currentViewRenderCoordinates = currentViewCore.getRend
erCoordinates(); |
| 649 float currentViewScrollX = currentViewRenderCoordinates.getScrollXPix(); |
| 650 float currentViewScrollY = currentViewRenderCoordinates.getScrollYPix(); |
| 651 |
| 652 ContentViewCore backgroundViewCore = getContentViewCore(); |
| 653 RenderCoordinates backgroundViewRenderCoordinates = |
| 654 backgroundViewCore.getRenderCoordinates(); |
| 655 float maxHorizontalScroll = backgroundViewRenderCoordinates.getMaxHorizo
ntalScrollPix(); |
| 656 float maxVerticalScroll = backgroundViewRenderCoordinates.getMaxVertical
ScrollPix(); |
| 657 |
| 658 // Bound the scroll offsets to max scroll values. |
| 659 float newScrollX = Math.min(currentViewScrollX, maxHorizontalScroll); |
| 660 float newScrollY = Math.min(currentViewScrollY, maxVerticalScroll); |
| 661 |
| 662 float backgroundViewScrollX = backgroundViewRenderCoordinates.getScrollX
Pix(); |
| 663 float backgroundViewScrollY = backgroundViewRenderCoordinates.getScrollY
Pix(); |
| 664 int scrollByX = Math.round(backgroundViewRenderCoordinates.fromPixToLoca
lCss( |
| 665 newScrollX - backgroundViewScrollX)); |
| 666 int scrollByY = Math.round(backgroundViewRenderCoordinates.fromPixToLoca
lCss( |
| 667 newScrollY - backgroundViewScrollY)); |
| 668 |
| 669 if (Math.abs(scrollByX) <= 1 && Math.abs(scrollByY) <= 1) return false; |
| 670 |
| 671 if (!mOriginalPageZoomed) { |
| 672 backgroundViewCore.scrollBy(Math.round(newScrollX - backgroundViewSc
rollX), |
| 673 Math.round(newScrollY - backgroundViewScrollY)); |
| 674 } else { |
| 675 backgroundViewCore.scrollTo(Math.round(newScrollX), Math.round(newSc
rollY)); |
| 676 } |
| 677 return true; |
| 678 } |
| 679 |
| 680 private void swapInBackgroundViewAfterScroll() { |
| 681 getContentViewCore().addGestureStateListener(mBackgroundGestureStateList
ener); |
| 682 syncPageStateAndSwapIfReady(); |
| 683 } |
| 684 |
| 685 // Set scroll offset and zoom values from current cvc and swap the cvc if th
e both are synced. |
| 686 private void syncPageStateAndSwapIfReady() { |
| 687 // This function is asynchronously called from various places. |
| 688 // So make sure we have background content view before doing anything. |
| 689 if (!hasPendingBackgroundPage() || !isPageSwappingInProgress()) return; |
| 690 |
| 691 ContentViewCore currentViewCore = mTab.getContentViewCore(); |
| 692 if (currentViewCore == null || currentViewCore.isScrollInProgress()) { |
| 693 // We'll comeback here on scrollEnded and flingEndGesture. |
| 694 return; |
| 695 } |
| 696 |
| 697 if (transferPageScaleFactor()) return; |
| 698 |
| 699 if (transferScrollOffset()) return; |
| 700 |
| 701 RenderCoordinates currentViewRenderCoordinates = currentViewCore.getRend
erCoordinates(); |
| 702 int currentViewScrollY = currentViewRenderCoordinates.getScrollYPixInt()
; |
| 703 |
| 704 int state; |
| 705 if (currentViewScrollY == 0) { |
| 706 state = PREVIEW_SCROLL_STATE_NO_SCROLL; |
| 707 } else if (currentViewScrollY >= currentViewRenderCoordinates.getMaxVert
icalScrollPix()) { |
| 708 state = PREVIEW_SCROLL_STATE_SCROLL_AT_BOTTOM; |
| 709 } else { |
| 710 state = PREVIEW_SCROLL_STATE_SCROLL; |
| 711 } |
| 712 RecordHistogram.recordEnumeratedHistogram( |
| 713 "InstantSearchClicks.PreviewScrollState", state, PREVIEW_SCROLL_
STATE_BOUNDARY); |
| 714 RecordHistogram.recordEnumeratedHistogram( |
| 715 "InstantSearchClicks.ReasonForSwap", SWAP_REASON_REGULAR, SWAP_R
EASON_BOUNDARY); |
| 716 |
| 717 // We are ready swap the view now. |
| 718 swapInBackgroundView(); |
| 719 } |
| 720 |
| 721 private void swapInBackgroundView() { |
| 722 // This function is asynchronously called from various places. |
| 723 // So make sure we have background content view before doing anything. |
| 724 if (!hasPendingBackgroundPage()) return; |
| 725 |
| 726 if (mTab.getContentViewCore() != null) { |
| 727 mTab.getContentViewCore().removeGestureStateListener(mCurrentViewGes
tureStateListener); |
| 728 } |
| 729 getContentViewCore().removeGestureStateListener(mBackgroundGestureStateL
istener); |
| 730 assert mDelegate != null; |
| 731 |
| 732 // Make sure the ContentViewCore is visible. |
| 733 getContentViewCore().setDrawsContent(true); |
| 734 |
| 735 // Merge history stack before swap. |
| 736 nativeMergeHistoryFrom(mNativeBackgroundContentViewHelperPtr, mTab.getWe
bContents()); |
| 737 ContentViewCore cvc = releaseContentViewCore(); |
| 738 mDelegate.onBackgroundViewReady(cvc, mDidStartLoad, mDidFinishLoad, mLoa
dProgress); |
| 739 |
| 740 RecordHistogram.recordTimesHistogram("InstantSearchClicks.TimeToSwap", |
| 741 System.currentTimeMillis() - mBackgroundLoadStartTimeStampMs, |
| 742 TimeUnit.MILLISECONDS); |
| 743 |
| 744 // Content view is released already, but still calling this to clear som
e state. |
| 745 destroyContentViewCore(); |
| 746 |
| 747 // We need to include metrics from orignal page after swap too. For exam
ple, single tap |
| 748 // metric should include taps in both preview and original page after sw
ap. This we reset |
| 749 // when tab commits a new navigation. |
| 750 setLogUma(); |
| 751 } |
| 752 |
| 753 /** Stop the current navigation. */ |
| 754 public void stopLoading() { |
| 755 destroyContentViewCore(); |
| 756 } |
| 757 |
| 758 /** |
| 759 * Forces swapping of content views. |
| 760 */ |
| 761 public void forceSwappingContentViews() { |
| 762 RecordHistogram.recordEnumeratedHistogram( |
| 763 "InstantSearchClicks.ReasonForSwap", SWAP_REASON_FORCE, SWAP_REA
SON_BOUNDARY); |
| 764 swapInBackgroundView(); |
| 765 } |
| 766 |
| 767 /** |
| 768 * Calls 'unload' handler and deletes the webContents. |
| 769 * @param webContents The webcontents to be deleted. |
| 770 */ |
| 771 public void unloadAndDeleteWebContents(WebContents webContents) { |
| 772 nativeUnloadAndDeleteWebContents(mNativeBackgroundContentViewHelperPtr,
webContents); |
| 773 } |
| 774 |
| 775 /** |
| 776 * Helper to reset the state of the object. |
| 777 */ |
| 778 private void reset() { |
| 779 mContentViewCore = null; |
| 780 if (mWebContentsObserver != null) { |
| 781 mWebContentsObserver.destroy(); |
| 782 mWebContentsObserver = null; |
| 783 } |
| 784 |
| 785 mPreviewLoaded = false; |
| 786 mPaintedNonEmpty = false; |
| 787 mNumRendererFramesReceived = 0; |
| 788 resetLogUma(); |
| 789 mHandler.removeCallbacks(mSwapOnTimeout); |
| 790 mOriginalPageZoomed = false; |
| 791 } |
| 792 |
| 793 @Override |
| 794 public void onLoadProgressChanged(Tab tab, int progress) { |
| 795 if (!hasPendingBackgroundPage()) return; |
| 796 if (progress >= 100) { |
| 797 mPreviewLoaded = true; |
| 798 trySwappingBackgroundView(); |
| 799 } |
| 800 } |
| 801 |
| 802 @Override |
| 803 public void onContentChanged(Tab tab) { |
| 804 if (mCurrentViewWebContentsObserver != null) { |
| 805 mCurrentViewWebContentsObserver.destroy(); |
| 806 } |
| 807 if (tab.getContentViewCore() == null) return; |
| 808 |
| 809 loadOriginalUrlIfPreview(mTab.getUrl()); |
| 810 |
| 811 mCurrentViewWebContentsObserver = new WebContentsObserver(mTab.getWebCon
tents()) { |
| 812 @Override |
| 813 public void didFailLoad(boolean isProvisionalLoad, |
| 814 boolean isMainFrame, int errorCode, String description, Stri
ng failingUrl) { |
| 815 if (isMainFrame && hasPendingBackgroundPage()) { |
| 816 if (TextUtils.equals(mContentViewCore.getWebContents().getUr
l(), |
| 817 getOriginalUrlForPreviewUrl(failingUrl))) { |
| 818 forceSwappingContentViews(); |
| 819 RecordHistogram.recordEnumeratedHistogram( |
| 820 "InstantSearchClicks.ReasonForSwap", |
| 821 SWAP_REASON_ON_PREVIEW_FAIL, SWAP_REASON_BOUNDAR
Y); |
| 822 } |
| 823 } |
| 824 } |
| 825 |
| 826 @Override |
| 827 public void didStartProvisionalLoadForFrame(long frameId, long paren
tFrameId, |
| 828 boolean isMainFrame, String validatedUrl, boolean isErrorPag
e, |
| 829 boolean isIframeSrcdoc) { |
| 830 if (isMainFrame) { |
| 831 if (mTab.getContentViewCore() != null) { |
| 832 loadOriginalUrlIfPreview(validatedUrl); |
| 833 } else { |
| 834 destroyContentViewCore(); |
| 835 } |
| 836 } |
| 837 } |
| 838 |
| 839 @Override |
| 840 public void didCommitProvisionalLoadForFrame( |
| 841 long frameId, boolean isMainFrame, String url, int transitio
nType) { |
| 842 if (!isMainFrame) return; |
| 843 |
| 844 if (!hasPendingBackgroundPage() && !isInstantSearchClicksControl
Url(url)) { |
| 845 resetLogUma(); |
| 846 } |
| 847 |
| 848 if (hasPendingBackgroundPage() && getOriginalUrlForPreviewUrl(ur
l) == null) { |
| 849 // We don't destroy the contentView in onDidStartProvisional
LoadForFrame, if the |
| 850 // url is navigating to non-preview URL. We instead do that
here. Some urls |
| 851 // which are handled using intents, the url is never commite
d. In which case we |
| 852 // still want to swap to happen. |
| 853 destroyContentViewCore(); |
| 854 } |
| 855 } |
| 856 }; |
| 857 } |
| 858 |
| 859 @Override |
| 860 public void onDestroyed(Tab tab) { |
| 861 destroy(); |
| 862 } |
| 863 |
| 864 @CalledByNative |
| 865 private long getNativePtr() { |
| 866 return mNativeBackgroundContentViewHelperPtr; |
| 867 } |
| 868 |
| 869 public void recordBack() { |
| 870 if (mRecordUma) RecordUserAction.record("MobilePreviewPageBack"); |
| 871 } |
| 872 |
| 873 public void recordReload() { |
| 874 if (mRecordUma) RecordUserAction.record("MobilePreviewPageReload"); |
| 875 } |
| 876 |
| 877 public void recordTabClose() { |
| 878 if (mRecordUma) RecordUserAction.record("MobilePreviewPageTabClose"); |
| 879 } |
| 880 |
| 881 public void recordSingleTap() { |
| 882 if (mRecordUma) RecordUserAction.record("MobilePreviewPageSingleTap"); |
| 883 } |
| 884 |
| 885 private native long nativeInit(); |
| 886 private native void nativeDestroy(long nativeBackgroundContentViewHelper); |
| 887 private native void nativeSetWebContents(long nativeBackgroundContentViewHel
per, |
| 888 ContentViewCore contentViewCore, WebContentsDelegateAndroid delegate
); |
| 889 private native void nativeDestroyWebContents(long nativeBackgroundContentVie
wHelper); |
| 890 private native void nativeReleaseWebContents(long nativeBackgroundContentVie
wHelper); |
| 891 private native void nativeMergeHistoryFrom(long nativeBackgroundContentViewH
elper, |
| 892 WebContents webContents); |
| 893 private native void nativeUnloadAndDeleteWebContents(long nativeBackgroundCo
ntentViewHelper, |
| 894 WebContents webContents); |
| 895 private native int nativeGetSwapTimeoutMs(long nativeBackgroundContentViewHe
lper); |
| 896 private native int nativeGetNumFramesNeededForSwap(long nativeBackgroundCont
entViewHelper); |
| 897 } |
OLD | NEW |