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.annotation.TargetApi; |
| 8 import android.content.Context; |
| 9 import android.content.Intent; |
| 10 import android.graphics.Rect; |
| 11 import android.media.AudioManager; |
| 12 import android.os.Build; |
| 13 import android.os.Handler; |
| 14 import android.os.Message; |
| 15 import android.text.TextUtils; |
| 16 import android.view.ActionMode; |
| 17 import android.view.ContextMenu; |
| 18 import android.view.KeyEvent; |
| 19 import android.view.View; |
| 20 |
| 21 import com.google.android.apps.chrome.R; |
| 22 |
| 23 import org.chromium.base.Log; |
| 24 import org.chromium.base.TraceEvent; |
| 25 import org.chromium.base.VisibleForTesting; |
| 26 import org.chromium.base.metrics.RecordUserAction; |
| 27 import org.chromium.chrome.browser.ChromeActivity; |
| 28 import org.chromium.chrome.browser.ChromeMobileApplication; |
| 29 import org.chromium.chrome.browser.CompositorChromeActivity; |
| 30 import org.chromium.chrome.browser.EmptyTabObserver; |
| 31 import org.chromium.chrome.browser.FrozenNativePage; |
| 32 import org.chromium.chrome.browser.IntentHandler.TabOpenType; |
| 33 import org.chromium.chrome.browser.NativePage; |
| 34 import org.chromium.chrome.browser.Tab; |
| 35 import org.chromium.chrome.browser.TabObserver; |
| 36 import org.chromium.chrome.browser.TabState; |
| 37 import org.chromium.chrome.browser.TabUma; |
| 38 import org.chromium.chrome.browser.TabUma.TabCreationState; |
| 39 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator; |
| 40 import org.chromium.chrome.browser.contextmenu.ContextMenuParams; |
| 41 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator; |
| 42 import org.chromium.chrome.browser.contextualsearch.ContextualSearchTabHelper; |
| 43 import org.chromium.chrome.browser.crash.MinidumpUploadService; |
| 44 import org.chromium.chrome.browser.dom_distiller.ReaderModeActivityDelegate; |
| 45 import org.chromium.chrome.browser.dom_distiller.ReaderModeManager; |
| 46 import org.chromium.chrome.browser.download.ChromeDownloadDelegate; |
| 47 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler; |
| 48 import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.Overrid
eUrlLoadingResult; |
| 49 import org.chromium.chrome.browser.externalnav.ExternalNavigationParams; |
| 50 import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
| 51 import org.chromium.chrome.browser.media.MediaNotificationService; |
| 52 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; |
| 53 import org.chromium.chrome.browser.ntp.NativePageAssassin; |
| 54 import org.chromium.chrome.browser.ntp.NativePageFactory; |
| 55 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader; |
| 56 import org.chromium.chrome.browser.policy.PolicyAuditor; |
| 57 import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent; |
| 58 import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| 59 import org.chromium.chrome.browser.rlz.RevenueStats; |
| 60 import org.chromium.chrome.browser.search_engines.TemplateUrlService; |
| 61 import org.chromium.chrome.browser.tab.BackgroundContentViewHelper.BackgroundCon
tentViewDelegate; |
| 62 import org.chromium.chrome.browser.tabmodel.TabModel; |
| 63 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| 64 import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
| 65 import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
| 66 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| 67 import org.chromium.components.navigation_interception.InterceptNavigationDelega
te; |
| 68 import org.chromium.components.navigation_interception.NavigationParams; |
| 69 import org.chromium.content.browser.ActivityContentVideoViewClient; |
| 70 import org.chromium.content.browser.ContentVideoViewClient; |
| 71 import org.chromium.content.browser.ContentViewClient; |
| 72 import org.chromium.content.browser.ContentViewCore; |
| 73 import org.chromium.content.browser.SelectActionMode; |
| 74 import org.chromium.content.browser.SelectActionModeCallback.ActionHandler; |
| 75 import org.chromium.content.browser.crypto.CipherFactory; |
| 76 import org.chromium.content_public.browser.GestureStateListener; |
| 77 import org.chromium.content_public.browser.InvalidateTypes; |
| 78 import org.chromium.content_public.browser.LoadUrlParams; |
| 79 import org.chromium.content_public.browser.NavigationController; |
| 80 import org.chromium.content_public.browser.WebContents; |
| 81 import org.chromium.content_public.browser.WebContentsObserver; |
| 82 import org.chromium.content_public.common.ConsoleMessageLevel; |
| 83 import org.chromium.content_public.common.Referrer; |
| 84 import org.chromium.ui.WindowOpenDisposition; |
| 85 import org.chromium.ui.base.PageTransition; |
| 86 import org.chromium.ui.base.WindowAndroid; |
| 87 |
| 88 import java.util.Locale; |
| 89 |
| 90 /** |
| 91 * A representation of a Tab for Chrome. This manages wrapping a WebContents and
interacting with |
| 92 * most of Chromium while representing a consistent version of web content to th
e front end. |
| 93 */ |
| 94 public class ChromeTab extends Tab { |
| 95 public static final int NTP_TAB_ID = -2; |
| 96 |
| 97 private static final String TAG = "ChromeTab"; |
| 98 |
| 99 // URL didFailLoad error code. Should match the value in net_error_list.h. |
| 100 public static final int BLOCKED_BY_ADMINISTRATOR = -22; |
| 101 |
| 102 public static final String PAGESPEED_PASSTHROUGH_HEADER = |
| 103 "X-PSA-Client-Options: v=1,m=1\nCache-Control: no-cache"; |
| 104 |
| 105 private static final int MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD = 1; |
| 106 |
| 107 /** The maximum amount of time to wait for a page to load before entering fu
llscreen. -1 means |
| 108 * wait until the page finishes loading. */ |
| 109 private static final long MAX_FULLSCREEN_LOAD_DELAY_MS = 3000; |
| 110 |
| 111 private ReaderModeManager mReaderModeManager; |
| 112 |
| 113 private TabRedirectHandler mTabRedirectHandler; |
| 114 |
| 115 private ChromeDownloadDelegate mDownloadDelegate; |
| 116 |
| 117 private boolean mIsFullscreenWaitingForLoad = false; |
| 118 private ExternalNavigationHandler.OverrideUrlLoadingResult mLastOverrideUrlL
oadingResult = |
| 119 ExternalNavigationHandler.OverrideUrlLoadingResult.NO_OVERRIDE; |
| 120 |
| 121 /** |
| 122 * Whether didCommitProvisionalLoadForFrame() hasn't yet been called for the
current native page |
| 123 * (page A). To decrease latency, we show native pages in both loadUrl() and |
| 124 * didCommitProvisionalLoadForFrame(). However, we mustn't show a new native
page (page B) in |
| 125 * loadUrl() if the current native page hasn't yet been committed. Otherwise
, we'll show each |
| 126 * page twice (A, B, A, B): the first two times in loadUrl(), the second two
times in |
| 127 * didCommitProvisionalLoadForFrame(). |
| 128 */ |
| 129 private boolean mIsNativePageCommitPending; |
| 130 |
| 131 protected final ChromeActivity mActivity; |
| 132 |
| 133 private WebContentsObserver mWebContentsObserver; |
| 134 |
| 135 private Handler mHandler; |
| 136 |
| 137 private final Runnable mCloseContentsRunnable = new Runnable() { |
| 138 @Override |
| 139 public void run() { |
| 140 mActivity.getTabModelSelector().closeTab(ChromeTab.this); |
| 141 } |
| 142 }; |
| 143 |
| 144 /** |
| 145 * The data reduction proxy was in use on the last page load if true. |
| 146 */ |
| 147 protected boolean mUsedSpdyProxy; |
| 148 |
| 149 /** |
| 150 * The data reduction proxy was in pass through mode on the last page load i
f true. |
| 151 */ |
| 152 protected boolean mUsedSpdyProxyWithPassthrough; |
| 153 |
| 154 /** |
| 155 * The last page load had request headers indicating that the data reduction
proxy should |
| 156 * be put in pass through mode, if true. |
| 157 */ |
| 158 private boolean mLastPageLoadHasSpdyProxyPassthroughHeaders; |
| 159 |
| 160 /** |
| 161 * Listens to gesture events fired by the ContentViewCore. |
| 162 */ |
| 163 private GestureStateListener mGestureStateListener; |
| 164 |
| 165 /** |
| 166 * The background content view helper which loads the original page in backg
round content view. |
| 167 */ |
| 168 private BackgroundContentViewHelper mBackgroundContentViewHelper; |
| 169 |
| 170 /** |
| 171 * The load progress of swapped in content view at the time of swap. |
| 172 */ |
| 173 private int mLoadProgressAtViewSwapInTime; |
| 174 |
| 175 /** |
| 176 * Whether forward history should be cleared after navigation is committed. |
| 177 */ |
| 178 private boolean mClearAllForwardHistoryRequired; |
| 179 |
| 180 private boolean mShouldClearRedirectHistoryForTabClobbering; |
| 181 |
| 182 /** |
| 183 * Basic constructor. This is hidden, so that explicitly named factory metho
ds are used to |
| 184 * create tabs. initialize() needs to be called afterwards to complete the s
econd level |
| 185 * initialization. |
| 186 * @param creationState State in which the tab is created, needed to initial
ize TabUma |
| 187 * accounting. When null, TabUma will not be initialize
d. |
| 188 * @param frozenState TabState that was saved when the Tab was last persiste
d to storage. |
| 189 */ |
| 190 protected ChromeTab( |
| 191 int id, ChromeActivity activity, boolean incognito, WindowAndroid na
tiveWindow, |
| 192 TabLaunchType type, int parentId, TabCreationState creationState, |
| 193 TabState frozenState) { |
| 194 super(id, parentId, incognito, activity, nativeWindow, type, frozenState
); |
| 195 |
| 196 if (frozenState == null) { |
| 197 assert type != TabLaunchType.FROM_RESTORE |
| 198 && creationState != TabCreationState.FROZEN_ON_RESTORE; |
| 199 } else { |
| 200 assert type == TabLaunchType.FROM_RESTORE |
| 201 && creationState == TabCreationState.FROZEN_ON_RESTORE; |
| 202 } |
| 203 |
| 204 addObserver(mTabObserver); |
| 205 mActivity = activity; |
| 206 mHandler = new Handler() { |
| 207 @Override |
| 208 public void handleMessage(Message msg) { |
| 209 if (msg == null) return; |
| 210 if (msg.what == MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD) { |
| 211 enableFullscreenAfterLoad(); |
| 212 } |
| 213 } |
| 214 }; |
| 215 setContentViewClient(createContentViewClient()); |
| 216 if (mActivity != null && creationState != null) { |
| 217 setTabUma(new TabUma( |
| 218 this, creationState, mActivity.getTabModelSelector().getMode
l(incognito))); |
| 219 } |
| 220 |
| 221 if (incognito) { |
| 222 CipherFactory.getInstance().triggerKeyGeneration(); |
| 223 } |
| 224 |
| 225 mReaderModeManager = new ReaderModeManager(this, activity); |
| 226 RevenueStats.getInstance().tabCreated(this); |
| 227 |
| 228 mTabRedirectHandler = new TabRedirectHandler(activity); |
| 229 |
| 230 ContextualSearchTabHelper.createForTab(this); |
| 231 if (nativeWindow != null) ThumbnailTabHelper.createForTab(this); |
| 232 } |
| 233 |
| 234 /** |
| 235 * Creates a minimal {@link ChromeTab} for testing. Do not use outside testi
ng. |
| 236 * |
| 237 * @param id The id of the tab. |
| 238 * @param incognito Whether the tab is incognito. |
| 239 */ |
| 240 @VisibleForTesting |
| 241 public ChromeTab(int id, boolean incognito) { |
| 242 super(id, incognito, null, null); |
| 243 mActivity = null; |
| 244 mTabRedirectHandler = new TabRedirectHandler(null); |
| 245 } |
| 246 |
| 247 /** |
| 248 * Creates a fresh tab. initialize() needs to be called afterwards to comple
te the second level |
| 249 * initialization. |
| 250 * @param initiallyHidden true iff the tab being created is initially in bac
kground |
| 251 */ |
| 252 public static ChromeTab createLiveTab(int id, ChromeActivity activity, boole
an incognito, |
| 253 WindowAndroid nativeWindow, TabLaunchType type, int parentId, boolea
n initiallyHidden) { |
| 254 return new ChromeTab(id, activity, incognito, nativeWindow, type, parent
Id, |
| 255 initiallyHidden ? TabCreationState.LIVE_IN_BACKGROUND : |
| 256 TabCreationState.LIVE_IN_FOREGROUND, null); |
| 257 } |
| 258 |
| 259 /** |
| 260 * Creates a new, "frozen" tab from a saved state. This can be used for back
ground tabs restored |
| 261 * on cold start that should be loaded when switched to. initialize() needs
to be called |
| 262 * afterwards to complete the second level initialization. |
| 263 */ |
| 264 public static ChromeTab createFrozenTabFromState( |
| 265 int id, ChromeActivity activity, boolean incognito, |
| 266 WindowAndroid nativeWindow, int parentId, TabState state) { |
| 267 assert state != null; |
| 268 return new ChromeTab(id, activity, incognito, nativeWindow, |
| 269 TabLaunchType.FROM_RESTORE, parentId, TabCreationState.FROZEN_ON
_RESTORE, |
| 270 state); |
| 271 } |
| 272 |
| 273 /** |
| 274 * Creates a new tab to be loaded lazily. This can be used for tabs opened i
n the background |
| 275 * that should be loaded when switched to. initialize() needs to be called a
fterwards to |
| 276 * complete the second level initialization. |
| 277 */ |
| 278 public static ChromeTab createTabForLazyLoad(ChromeActivity activity, boolea
n incognito, |
| 279 WindowAndroid nativeWindow, TabLaunchType type, int parentId, |
| 280 LoadUrlParams loadUrlParams) { |
| 281 ChromeTab tab = new ChromeTab( |
| 282 INVALID_TAB_ID, activity, incognito, nativeWindow, type, parentI
d, |
| 283 TabCreationState.FROZEN_FOR_LAZY_LOAD, null); |
| 284 tab.setPendingLoadParams(loadUrlParams); |
| 285 return tab; |
| 286 } |
| 287 |
| 288 public static ChromeTab fromTab(Tab tab) { |
| 289 return (ChromeTab) tab; |
| 290 } |
| 291 |
| 292 /** |
| 293 * Initializes the ChromeTab after construction with an existing ContentView
Core. |
| 294 */ |
| 295 @Override |
| 296 protected void internalInit() { |
| 297 super.internalInit(); |
| 298 if (mBackgroundContentViewHelper == null) { |
| 299 BackgroundContentViewDelegate delegate = new BackgroundContentViewDe
legate() { |
| 300 @Override |
| 301 public void onBackgroundViewReady( |
| 302 ContentViewCore cvc, boolean didStartLoad, boolean didFi
nishLoad, |
| 303 int progress) { |
| 304 WebContents previewWebContents = getWebContents(); |
| 305 swapContentViewCore(cvc, false, didStartLoad, didFinishLoad)
; |
| 306 mLoadProgressAtViewSwapInTime = progress; |
| 307 mBackgroundContentViewHelper.unloadAndDeleteWebContents(prev
iewWebContents); |
| 308 |
| 309 // Enter to fullscreen. |
| 310 mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD)
; |
| 311 mHandler.sendEmptyMessageDelayed( |
| 312 MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_
LOAD_DELAY_MS); |
| 313 updateFullscreenEnabledState(); |
| 314 } |
| 315 |
| 316 @Override |
| 317 public void onLoadProgressChanged(int progress) { |
| 318 notifyLoadProgress(getProgress()); |
| 319 } |
| 320 }; |
| 321 mBackgroundContentViewHelper = new BackgroundContentViewHelper( |
| 322 getWindowAndroid(), this, delegate); |
| 323 } |
| 324 } |
| 325 |
| 326 /** |
| 327 * Remember if the last load used the data reduction proxy, and if so, |
| 328 * also remember if it used pass through mode. |
| 329 */ |
| 330 private void maybeSetDataReductionProxyUsed() { |
| 331 // Ignore internal URLs. |
| 332 String url = getUrl(); |
| 333 if (url != null && url.toLowerCase(Locale.US).startsWith("chrome://")) { |
| 334 return; |
| 335 } |
| 336 mUsedSpdyProxy = false; |
| 337 mUsedSpdyProxyWithPassthrough = false; |
| 338 if (isSpdyProxyEnabledForUrl(url)) { |
| 339 mUsedSpdyProxy = true; |
| 340 if (mLastPageLoadHasSpdyProxyPassthroughHeaders) { |
| 341 mLastPageLoadHasSpdyProxyPassthroughHeaders = false; |
| 342 mUsedSpdyProxyWithPassthrough = true; |
| 343 } |
| 344 } |
| 345 } |
| 346 |
| 347 @Override |
| 348 protected void openNewTab( |
| 349 LoadUrlParams params, TabLaunchType launchType, Tab parentTab, boole
an incognito) { |
| 350 mActivity.getTabModelSelector().openNewTab(params, launchType, parentTab
, incognito); |
| 351 } |
| 352 |
| 353 @Override |
| 354 protected TabChromeWebContentsDelegateAndroid createWebContentsDelegate() { |
| 355 return new TabChromeWebContentsDelegateAndroidImpl(); |
| 356 } |
| 357 |
| 358 /** |
| 359 * An implementation for this tab's web contents delegate. |
| 360 */ |
| 361 public class TabChromeWebContentsDelegateAndroidImpl |
| 362 extends TabChromeWebContentsDelegateAndroid { |
| 363 /** |
| 364 * This method is meant to be overridden by DocumentTab because the |
| 365 * TabModelSelector returned by the activity is not correct. |
| 366 * TODO(dfalcantara): remove this when DocumentActivity.getTabModelSelec
tor() |
| 367 * will return the right TabModelSelector. |
| 368 */ |
| 369 protected TabModel getTabModel() { |
| 370 return mActivity.getTabModelSelector().getModel(isIncognito()); |
| 371 } |
| 372 |
| 373 @Override |
| 374 public boolean addNewContents(WebContents sourceWebContents, WebContents
webContents, |
| 375 int disposition, Rect initialPosition, boolean userGesture) { |
| 376 if (isClosing()) return false; |
| 377 |
| 378 // TODO(johnme): Open tabs in same order as Chrome. |
| 379 Tab tab = mActivity.getTabCreator(isIncognito()).createTabWithWebCon
tents( |
| 380 webContents, getId(), TabLaunchType.FROM_LONGPRESS_FOREGROUN
D); |
| 381 |
| 382 if (tab == null) return false; |
| 383 |
| 384 if (disposition == WindowOpenDisposition.NEW_POPUP) { |
| 385 PolicyAuditor auditor = |
| 386 ((ChromeMobileApplication) getApplicationContext()).getP
olicyAuditor(); |
| 387 auditor.notifyAuditEvent(getApplicationContext(), AuditEvent.OPE
N_POPUP_URL_SUCCESS, |
| 388 tab.getUrl(), ""); |
| 389 } |
| 390 |
| 391 return true; |
| 392 } |
| 393 |
| 394 @Override |
| 395 public void activateContents() { |
| 396 boolean activityIsDestroyed = false; |
| 397 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| 398 activityIsDestroyed = mActivity.isDestroyed(); |
| 399 } |
| 400 if (activityIsDestroyed || !isInitialized()) { |
| 401 Log.e(TAG, "Activity destroyed before calling activateContents()
. Bailing out."); |
| 402 return; |
| 403 } |
| 404 |
| 405 TabModel model = getTabModel(); |
| 406 int index = model.indexOf(ChromeTab.this); |
| 407 if (index == TabModel.INVALID_TAB_INDEX) return; |
| 408 |
| 409 TabModelUtils.setIndex(model, index); |
| 410 |
| 411 // This intent is sent in order to get the activity back to the fore
ground if it was |
| 412 // not already. The previous call will activate the right tab in the
context of the |
| 413 // TabModel but will only show the tab to the user if Chrome was alr
eady in the |
| 414 // foreground. |
| 415 // The intent is getting the tabId mostly because it does not cost m
uch to do so. |
| 416 // When receiving the intent, the tab associated with the tabId shou
ld already be |
| 417 // active. |
| 418 // Note that calling only the intent in order to activate the tab is
slightly slower |
| 419 // because it will change the tab when the intent is handled, which
happens after |
| 420 // Chrome gets back to the foreground. |
| 421 Intent newIntent = new Intent(); |
| 422 newIntent.setAction(Intent.ACTION_MAIN); |
| 423 newIntent.setPackage(mActivity.getPackageName()); |
| 424 newIntent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(), |
| 425 ChromeTab.this.getId()); |
| 426 |
| 427 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 428 |
| 429 getApplicationContext().startActivity(newIntent); |
| 430 } |
| 431 |
| 432 @Override |
| 433 public void navigationStateChanged(int flags) { |
| 434 if ((flags & InvalidateTypes.TAB) != 0) { |
| 435 MediaNotificationService.updateMediaNotificationForTab( |
| 436 getApplicationContext(), getId(), isCapturingAudio(), |
| 437 isCapturingVideo(), hasAudibleAudio(), getUrl()); |
| 438 } |
| 439 super.navigationStateChanged(flags); |
| 440 } |
| 441 |
| 442 @Override |
| 443 public void onLoadProgressChanged(int progress) { |
| 444 if (!isLoading()) return; |
| 445 if (progress >= mLoadProgressAtViewSwapInTime) mLoadProgressAtViewSw
apInTime = 0; |
| 446 notifyLoadProgress(getProgress()); |
| 447 } |
| 448 |
| 449 @Override |
| 450 public void closeContents() { |
| 451 // Execute outside of callback, otherwise we end up deleting the nat
ive |
| 452 // objects in the middle of executing methods on them. |
| 453 mHandler.removeCallbacks(mCloseContentsRunnable); |
| 454 mHandler.post(mCloseContentsRunnable); |
| 455 } |
| 456 |
| 457 @Override |
| 458 public boolean takeFocus(boolean reverse) { |
| 459 if (reverse) { |
| 460 View menuButton = mActivity.findViewById(R.id.menu_button); |
| 461 if (menuButton == null || !menuButton.isShown()) { |
| 462 menuButton = mActivity.findViewById(R.id.document_menu_butto
n); |
| 463 } |
| 464 if (menuButton != null && menuButton.isShown()) { |
| 465 return menuButton.requestFocus(); |
| 466 } |
| 467 |
| 468 View tabSwitcherButton = mActivity.findViewById(R.id.tab_switche
r_button); |
| 469 if (tabSwitcherButton != null && tabSwitcherButton.isShown()) { |
| 470 return tabSwitcherButton.requestFocus(); |
| 471 } |
| 472 } else { |
| 473 View urlBar = mActivity.findViewById(R.id.url_bar); |
| 474 if (urlBar != null) return urlBar.requestFocus(); |
| 475 } |
| 476 return false; |
| 477 } |
| 478 |
| 479 @Override |
| 480 public void handleKeyboardEvent(KeyEvent event) { |
| 481 if (event.getAction() == KeyEvent.ACTION_DOWN) { |
| 482 if (mActivity.onKeyDown(event.getKeyCode(), event)) return; |
| 483 |
| 484 // Handle the Escape key here (instead of in KeyboardShortcuts.j
ava), so it doesn't |
| 485 // interfere with other parts of the activity (e.g. the URL bar)
. |
| 486 if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNo
Modifiers()) { |
| 487 WebContents wc = getWebContents(); |
| 488 if (wc != null) wc.stop(); |
| 489 return; |
| 490 } |
| 491 } |
| 492 handleMediaKey(event); |
| 493 } |
| 494 |
| 495 /** |
| 496 * Redispatches unhandled media keys. This allows bluetooth headphones w
ith play/pause or |
| 497 * other buttons to function correctly. |
| 498 */ |
| 499 @TargetApi(19) |
| 500 private void handleMediaKey(KeyEvent e) { |
| 501 if (Build.VERSION.SDK_INT < 19) return; |
| 502 switch (e.getKeyCode()) { |
| 503 case KeyEvent.KEYCODE_MUTE: |
| 504 case KeyEvent.KEYCODE_HEADSETHOOK: |
| 505 case KeyEvent.KEYCODE_MEDIA_PLAY: |
| 506 case KeyEvent.KEYCODE_MEDIA_PAUSE: |
| 507 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
| 508 case KeyEvent.KEYCODE_MEDIA_STOP: |
| 509 case KeyEvent.KEYCODE_MEDIA_NEXT: |
| 510 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
| 511 case KeyEvent.KEYCODE_MEDIA_REWIND: |
| 512 case KeyEvent.KEYCODE_MEDIA_RECORD: |
| 513 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: |
| 514 case KeyEvent.KEYCODE_MEDIA_CLOSE: |
| 515 case KeyEvent.KEYCODE_MEDIA_EJECT: |
| 516 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: |
| 517 AudioManager am = (AudioManager) mActivity.getSystemService( |
| 518 Context.AUDIO_SERVICE); |
| 519 am.dispatchMediaKeyEvent(e); |
| 520 break; |
| 521 default: |
| 522 break; |
| 523 } |
| 524 } |
| 525 |
| 526 /** |
| 527 * @return Whether audio is being captured. |
| 528 */ |
| 529 private boolean isCapturingAudio() { |
| 530 return !isClosing() && super.nativeIsCapturingAudio(getWebContents()
); |
| 531 } |
| 532 |
| 533 /** |
| 534 * @return Whether video is being captured. |
| 535 */ |
| 536 private boolean isCapturingVideo() { |
| 537 return !isClosing() && super.nativeIsCapturingVideo(getWebContents()
); |
| 538 } |
| 539 |
| 540 /** |
| 541 * @return Whether audio is being played. |
| 542 */ |
| 543 private boolean hasAudibleAudio() { |
| 544 return !isClosing() && super.nativeHasAudibleAudio(getWebContents())
; |
| 545 } |
| 546 |
| 547 } |
| 548 |
| 549 /** |
| 550 * Check whether the context menu download should be intercepted. |
| 551 * |
| 552 * @param url URL to be downloaded. |
| 553 * @return whether the download should be intercepted. |
| 554 */ |
| 555 protected boolean shouldInterceptContextMenuDownload(String url) { |
| 556 return mDownloadDelegate.shouldInterceptContextMenuDownload(url); |
| 557 } |
| 558 |
| 559 private class ChromeTabChromeContextMenuItemDelegate extends TabChromeContex
tMenuItemDelegate { |
| 560 private boolean mIsImage; |
| 561 private boolean mIsVideo; |
| 562 |
| 563 public void setParamsInfo(boolean isImage, boolean isVideo) { |
| 564 mIsImage = isImage; |
| 565 mIsVideo = isVideo; |
| 566 } |
| 567 |
| 568 @Override |
| 569 public boolean isIncognitoSupported() { |
| 570 return PrefServiceBridge.getInstance().isIncognitoModeEnabled(); |
| 571 } |
| 572 |
| 573 @Override |
| 574 public boolean canLoadOriginalImage() { |
| 575 return mUsedSpdyProxy && !mUsedSpdyProxyWithPassthrough; |
| 576 } |
| 577 |
| 578 @Override |
| 579 public boolean startDownload(String url, boolean isLink) { |
| 580 if (isLink) { |
| 581 RecordUserAction.record("MobileContextMenuDownloadLink"); |
| 582 if (shouldInterceptContextMenuDownload(url)) { |
| 583 return false; |
| 584 } |
| 585 } else if (mIsImage) { |
| 586 RecordUserAction.record("MobileContextMenuDownloadImage"); |
| 587 } else if (mIsVideo) { |
| 588 RecordUserAction.record("MobileContextMenuDownloadVideo"); |
| 589 } |
| 590 return true; |
| 591 } |
| 592 |
| 593 @Override |
| 594 public void onSaveToClipboard(String text, boolean isUrl) { |
| 595 if (isUrl) { |
| 596 RecordUserAction.record("MobileContextMenuCopyLinkAddress"); |
| 597 } else { |
| 598 RecordUserAction.record("MobileContextMenuCopyLinkText"); |
| 599 } |
| 600 super.onSaveToClipboard(text, isUrl); |
| 601 } |
| 602 |
| 603 @Override |
| 604 public void onSaveImageToClipboard(String url) { |
| 605 RecordUserAction.record("MobileContextMenuSaveImage"); |
| 606 super.onSaveImageToClipboard(url); |
| 607 } |
| 608 |
| 609 @Override |
| 610 public void onOpenInNewTab(String url, Referrer referrer) { |
| 611 RecordUserAction.record("MobileContextMenuOpenLinkInNewTab"); |
| 612 RecordUserAction.record("MobileNewTabOpened"); |
| 613 LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| 614 loadUrlParams.setReferrer(referrer); |
| 615 mActivity.getTabModelSelector().openNewTab(loadUrlParams, |
| 616 TabLaunchType.FROM_LONGPRESS_BACKGROUND, ChromeTab.this, isI
ncognito()); |
| 617 } |
| 618 |
| 619 @Override |
| 620 public void onOpenInNewIncognitoTab(String url) { |
| 621 RecordUserAction.record("MobileContextMenuOpenLinkInIncognito"); |
| 622 RecordUserAction.record("MobileNewTabOpened"); |
| 623 mActivity.getTabModelSelector().openNewTab(new LoadUrlParams(url), |
| 624 TabLaunchType.FROM_LONGPRESS_FOREGROUND, ChromeTab.this, tru
e); |
| 625 } |
| 626 |
| 627 @Override |
| 628 public void onOpenImageUrl(String url, Referrer referrer) { |
| 629 RecordUserAction.record("MobileContextMenuViewImage"); |
| 630 super.onOpenImageUrl(url, referrer); |
| 631 } |
| 632 |
| 633 @Override |
| 634 public void onOpenImageInNewTab(String url, Referrer referrer) { |
| 635 boolean useOriginal = isSpdyProxyEnabledForUrl(url); |
| 636 RecordUserAction.record("MobileContextMenuOpenImageInNewTab"); |
| 637 if (useOriginal) { |
| 638 RecordUserAction.record("MobileContextMenuOpenOriginalImageInNew
Tab"); |
| 639 } |
| 640 |
| 641 LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| 642 loadUrlParams.setVerbatimHeaders(useOriginal ? PAGESPEED_PASSTHROUGH
_HEADER : null); |
| 643 loadUrlParams.setReferrer(referrer); |
| 644 mActivity.getTabModelSelector().openNewTab(loadUrlParams, |
| 645 TabLaunchType.FROM_LONGPRESS_BACKGROUND, ChromeTab.this, isI
ncognito()); |
| 646 } |
| 647 |
| 648 @Override |
| 649 public void onSearchByImageInNewTab() { |
| 650 RecordUserAction.record("MobileContextMenuSearchByImage"); |
| 651 super.onSearchByImageInNewTab(); |
| 652 } |
| 653 } |
| 654 |
| 655 /** |
| 656 * This class is solely to track UMA stats. When we upstream UMA stats we c
an remove this. |
| 657 */ |
| 658 private static class ChromeTabChromeContextMenuPopulator extends ChromeConte
xtMenuPopulator { |
| 659 private final ChromeTabChromeContextMenuItemDelegate mDelegate; |
| 660 |
| 661 public ChromeTabChromeContextMenuPopulator( |
| 662 ChromeTabChromeContextMenuItemDelegate delegate) { |
| 663 super(delegate); |
| 664 |
| 665 mDelegate = delegate; |
| 666 } |
| 667 |
| 668 @Override |
| 669 public void buildContextMenu(ContextMenu menu, Context context, |
| 670 ContextMenuParams params) { |
| 671 if (params.isAnchor()) { |
| 672 RecordUserAction.record("MobileContextMenuLink"); |
| 673 } else if (params.isImage()) { |
| 674 RecordUserAction.record("MobileContextMenuImage"); |
| 675 } else if (params.isSelectedText()) { |
| 676 RecordUserAction.record("MobileContextMenuText"); |
| 677 } else if (params.isVideo()) { |
| 678 RecordUserAction.record("MobileContextMenuVideo"); |
| 679 } |
| 680 |
| 681 mDelegate.setParamsInfo(params.isImage(), params.isVideo()); |
| 682 super.buildContextMenu(menu, context, params); |
| 683 } |
| 684 } |
| 685 |
| 686 @Override |
| 687 protected ContextMenuPopulator createContextMenuPopulator() { |
| 688 return new ChromeTabChromeContextMenuPopulator( |
| 689 new ChromeTabChromeContextMenuItemDelegate()); |
| 690 } |
| 691 |
| 692 /** @return The {@link BackgroundContentViewHelper} associated with the curr
ent tab. */ |
| 693 public BackgroundContentViewHelper getBackgroundContentViewHelper() { |
| 694 return mBackgroundContentViewHelper; |
| 695 } |
| 696 |
| 697 @VisibleForTesting |
| 698 public void setViewClientForTesting(ContentViewClient client) { |
| 699 setContentViewClient(client); |
| 700 } |
| 701 |
| 702 @VisibleForTesting |
| 703 public ContentViewClient getViewClientForTesting() { |
| 704 return getContentViewClient(); |
| 705 } |
| 706 |
| 707 private ContentViewClient createContentViewClient() { |
| 708 return new TabContentViewClient() { |
| 709 @Override |
| 710 public void onBackgroundColorChanged(int color) { |
| 711 ChromeTab.this.onBackgroundColorChanged(color); |
| 712 } |
| 713 |
| 714 @Override |
| 715 public void onOffsetsForFullscreenChanged( |
| 716 float topControlsOffsetY, float contentOffsetY, float overdr
awBottomHeight) { |
| 717 onOffsetsChanged(topControlsOffsetY, contentOffsetY, overdrawBot
tomHeight, |
| 718 isShowingSadTab()); |
| 719 } |
| 720 |
| 721 @Override |
| 722 public void performWebSearch(String searchQuery) { |
| 723 if (TextUtils.isEmpty(searchQuery)) return; |
| 724 String url = TemplateUrlService.getInstance().getUrlForSearchQue
ry(searchQuery); |
| 725 String headers = GeolocationHeader.getGeoHeader(getApplicationCo
ntext(), url, |
| 726 isIncognito()); |
| 727 |
| 728 LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| 729 loadUrlParams.setVerbatimHeaders(headers); |
| 730 loadUrlParams.setTransitionType(PageTransition.GENERATED); |
| 731 mActivity.getTabModelSelector().openNewTab(loadUrlParams, |
| 732 TabLaunchType.FROM_LONGPRESS_FOREGROUND, ChromeTab.this,
isIncognito()); |
| 733 } |
| 734 |
| 735 @Override |
| 736 public boolean doesPerformWebSearch() { |
| 737 return true; |
| 738 } |
| 739 |
| 740 @Override |
| 741 public SelectActionMode startActionMode( |
| 742 View view, ActionHandler actionHandler, boolean floating) { |
| 743 if (floating) return null; |
| 744 ChromeSelectActionModeCallback callback = |
| 745 new ChromeSelectActionModeCallback(view.getContext(), ac
tionHandler); |
| 746 ActionMode actionMode = view.startActionMode(callback); |
| 747 return actionMode != null ? new SelectActionMode(actionMode) : n
ull; |
| 748 } |
| 749 |
| 750 @Override |
| 751 public boolean supportsFloatingActionMode() { |
| 752 return false; |
| 753 } |
| 754 |
| 755 @Override |
| 756 public ContentVideoViewClient getContentVideoViewClient() { |
| 757 return new ActivityContentVideoViewClient(mActivity) { |
| 758 @Override |
| 759 public void enterFullscreenVideo(View view) { |
| 760 super.enterFullscreenVideo(view); |
| 761 FullscreenManager fullscreenManager = getFullscreenManag
er(); |
| 762 if (fullscreenManager != null) { |
| 763 fullscreenManager.setOverlayVideoMode(true); |
| 764 // Disable double tap for video. |
| 765 if (getContentViewCore() != null) { |
| 766 getContentViewCore().updateDoubleTapSupport(fals
e); |
| 767 } |
| 768 } |
| 769 } |
| 770 |
| 771 @Override |
| 772 public void exitFullscreenVideo() { |
| 773 FullscreenManager fullscreenManager = getFullscreenManag
er(); |
| 774 if (fullscreenManager != null) { |
| 775 fullscreenManager.setOverlayVideoMode(false); |
| 776 // Disable double tap for video. |
| 777 if (getContentViewCore() != null) { |
| 778 getContentViewCore().updateDoubleTapSupport(true
); |
| 779 } |
| 780 } |
| 781 super.exitFullscreenVideo(); |
| 782 } |
| 783 }; |
| 784 } |
| 785 }; |
| 786 } |
| 787 |
| 788 private WebContentsObserver createWebContentsObserver(WebContents webContent
s) { |
| 789 return new WebContentsObserver(webContents) { |
| 790 @Override |
| 791 public void didFinishLoad(long frameId, String validatedUrl, boolean
isMainFrame) { |
| 792 PolicyAuditor auditor = |
| 793 ((ChromeMobileApplication) getApplicationContext()).getP
olicyAuditor(); |
| 794 auditor.notifyAuditEvent( |
| 795 getApplicationContext(), AuditEvent.OPEN_URL_SUCCESS, va
lidatedUrl, ""); |
| 796 } |
| 797 |
| 798 @Override |
| 799 public void didFailLoad(boolean isProvisionalLoad, |
| 800 boolean isMainFrame, int errorCode, String description, Stri
ng failingUrl) { |
| 801 PolicyAuditor auditor = |
| 802 ((ChromeMobileApplication) getApplicationContext()).getP
olicyAuditor(); |
| 803 auditor.notifyAuditEvent(getApplicationContext(), AuditEvent.OPE
N_URL_FAILURE, |
| 804 failingUrl, description); |
| 805 if (errorCode == BLOCKED_BY_ADMINISTRATOR) { |
| 806 auditor.notifyAuditEvent( |
| 807 getApplicationContext(), AuditEvent.OPEN_URL_BLOCKED
, failingUrl, ""); |
| 808 } |
| 809 } |
| 810 |
| 811 @Override |
| 812 public void didCommitProvisionalLoadForFrame( |
| 813 long frameId, boolean isMainFrame, String url, int transitio
nType) { |
| 814 if (!isMainFrame) return; |
| 815 |
| 816 mIsNativePageCommitPending = false; |
| 817 boolean isReload = (transitionType == PageTransition.RELOAD); |
| 818 if (!maybeShowNativePage(url, isReload)) { |
| 819 showRenderedPage(); |
| 820 } |
| 821 |
| 822 if (!mBackgroundContentViewHelper.hasPendingBackgroundPage()) { |
| 823 mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD)
; |
| 824 mHandler.sendEmptyMessageDelayed( |
| 825 MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_
LOAD_DELAY_MS); |
| 826 updateFullscreenEnabledState(); |
| 827 } |
| 828 |
| 829 // http://crbug/426679 : if navigation is canceled due to intent
handling, we want |
| 830 // to go back to the last committed entry index which was saved
before the |
| 831 // navigation, and remove the empty entries from the navigation
history. |
| 832 if (mClearAllForwardHistoryRequired && getWebContents() != null)
{ |
| 833 NavigationController navigationController = |
| 834 getWebContents().getNavigationController(); |
| 835 int lastCommittedEntryIndex = getLastCommittedEntryIndex(); |
| 836 while (navigationController.canGoForward()) { |
| 837 boolean ret = navigationController.removeEntryAtIndex( |
| 838 lastCommittedEntryIndex + 1); |
| 839 assert ret; |
| 840 } |
| 841 } else if (mShouldClearRedirectHistoryForTabClobbering |
| 842 && getWebContents() != null) { |
| 843 // http://crbug/479056: Even if we clobber the current tab,
we want to remove |
| 844 // redirect history to be consistent. |
| 845 NavigationController navigationController = |
| 846 getWebContents().getNavigationController(); |
| 847 int indexBeforeRedirection = mTabRedirectHandler |
| 848 .getLastCommittedEntryIndexBeforeStartingNavigation(
); |
| 849 int lastCommittedEntryIndex = getLastCommittedEntryIndex(); |
| 850 for (int i = lastCommittedEntryIndex - 1; i > indexBeforeRed
irection; --i) { |
| 851 boolean ret = navigationController.removeEntryAtIndex(i)
; |
| 852 assert ret; |
| 853 } |
| 854 } |
| 855 mClearAllForwardHistoryRequired = false; |
| 856 mShouldClearRedirectHistoryForTabClobbering = false; |
| 857 } |
| 858 |
| 859 @Override |
| 860 public void didAttachInterstitialPage() { |
| 861 PolicyAuditor auditor = |
| 862 ((ChromeMobileApplication) getApplicationContext()).getP
olicyAuditor(); |
| 863 auditor.notifyCertificateFailure(getWebContents(), getApplicatio
nContext()); |
| 864 } |
| 865 |
| 866 @Override |
| 867 public void didDetachInterstitialPage() { |
| 868 if (!maybeShowNativePage(getUrl(), false)) { |
| 869 showRenderedPage(); |
| 870 } |
| 871 } |
| 872 |
| 873 @Override |
| 874 public void destroy() { |
| 875 MediaNotificationService.updateMediaNotificationForTab( |
| 876 getApplicationContext(), getId(), false, false, false, g
etUrl()); |
| 877 super.destroy(); |
| 878 } |
| 879 }; |
| 880 } |
| 881 |
| 882 @Override |
| 883 protected void didStartPageLoad(String validatedUrl, boolean showingErrorPag
e) { |
| 884 if (mBackgroundContentViewHelper.isPageSwappingInProgress()) { |
| 885 mLoadProgressAtViewSwapInTime = 0; |
| 886 } |
| 887 |
| 888 mIsFullscreenWaitingForLoad = !DomDistillerUrlUtils.isDistilledPage(vali
datedUrl); |
| 889 mLoadProgressAtViewSwapInTime = 0; |
| 890 |
| 891 super.didStartPageLoad(validatedUrl, showingErrorPage); |
| 892 } |
| 893 |
| 894 @Override |
| 895 protected void didFinishPageLoad() { |
| 896 // We should not mark finished if we have pending background page to swa
p in. |
| 897 // We'll instead call didFinishPageLoad after the background page is swa
pped in. |
| 898 if (mBackgroundContentViewHelper.hasPendingBackgroundPage()) return; |
| 899 |
| 900 mLoadProgressAtViewSwapInTime = 0; |
| 901 |
| 902 super.didFinishPageLoad(); |
| 903 |
| 904 // Handle the case where we were pre-renderered and the enable fullscree
n message was |
| 905 // never enqueued. |
| 906 if (mIsFullscreenWaitingForLoad |
| 907 && !mHandler.hasMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD) |
| 908 && !mBackgroundContentViewHelper.hasPendingBackgroundPage()) { |
| 909 mHandler.sendEmptyMessageDelayed( |
| 910 MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_LOAD_DEL
AY_MS); |
| 911 } |
| 912 |
| 913 maybeSetDataReductionProxyUsed(); |
| 914 } |
| 915 |
| 916 @Override |
| 917 protected void didFailPageLoad(int errorCode) { |
| 918 mLoadProgressAtViewSwapInTime = 0; |
| 919 cancelEnableFullscreenLoadDelay(); |
| 920 super.didFailPageLoad(errorCode); |
| 921 updateFullscreenEnabledState(); |
| 922 } |
| 923 |
| 924 private void cancelEnableFullscreenLoadDelay() { |
| 925 mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD); |
| 926 mIsFullscreenWaitingForLoad = false; |
| 927 } |
| 928 |
| 929 /** |
| 930 * Removes the enable fullscreen runnable from the UI queue and runs it imme
diately. |
| 931 */ |
| 932 @VisibleForTesting |
| 933 public void processEnableFullscreenRunnableForTest() { |
| 934 if (mHandler.hasMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD)) { |
| 935 mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD); |
| 936 enableFullscreenAfterLoad(); |
| 937 } |
| 938 } |
| 939 |
| 940 private void enableFullscreenAfterLoad() { |
| 941 if (!mIsFullscreenWaitingForLoad) return; |
| 942 |
| 943 mIsFullscreenWaitingForLoad = false; |
| 944 updateFullscreenEnabledState(); |
| 945 } |
| 946 |
| 947 @Override |
| 948 protected boolean isHidingTopControlsEnabled() { |
| 949 return super.isHidingTopControlsEnabled() && !mIsFullscreenWaitingForLo
ad; |
| 950 } |
| 951 |
| 952 @Override |
| 953 protected void handleTabCrash() { |
| 954 super.handleTabCrash(); |
| 955 |
| 956 // Update the most recent minidump file with the logcat. Doing this asyn
chronously |
| 957 // adds a race condition in the case of multiple simultaneously renderer
crashses |
| 958 // but because the data will be the same for all of them it is innocuous
. We can |
| 959 // attempt to do this regardless of whether it was a foreground tab in t
he event |
| 960 // that it's a real crash and not just android killing the tab. |
| 961 Context context = getApplicationContext(); |
| 962 Intent intent = MinidumpUploadService.createFindAndUploadLastCrashIntent
(context); |
| 963 context.startService(intent); |
| 964 RecordUserAction.record("MobileBreakpadUploadAttempt"); |
| 965 } |
| 966 |
| 967 @Override |
| 968 protected void setContentViewCore(ContentViewCore cvc) { |
| 969 try { |
| 970 TraceEvent.begin("ChromeTab.setContentViewCore"); |
| 971 super.setContentViewCore(cvc); |
| 972 mWebContentsObserver = createWebContentsObserver(cvc.getWebContents(
)); |
| 973 |
| 974 mDownloadDelegate = new ChromeDownloadDelegate(mActivity, |
| 975 mActivity.getTabModelSelector(), this); |
| 976 cvc.setDownloadDelegate(mDownloadDelegate); |
| 977 setInterceptNavigationDelegate(new InterceptNavigationDelegateImpl()
); |
| 978 |
| 979 if (mGestureStateListener == null) mGestureStateListener = createGes
tureStateListener(); |
| 980 cvc.addGestureStateListener(mGestureStateListener); |
| 981 } finally { |
| 982 TraceEvent.end("ChromeTab.setContentViewCore"); |
| 983 } |
| 984 } |
| 985 |
| 986 private GestureStateListener createGestureStateListener() { |
| 987 return new GestureStateListener() { |
| 988 @Override |
| 989 public void onFlingStartGesture(int vx, int vy, int scrollOffsetY, i
nt scrollExtentY) { |
| 990 onScrollingStateChanged(); |
| 991 } |
| 992 |
| 993 @Override |
| 994 public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY)
{ |
| 995 onScrollingStateChanged(); |
| 996 } |
| 997 |
| 998 @Override |
| 999 public void onScrollStarted(int scrollOffsetY, int scrollExtentY) { |
| 1000 onScrollingStateChanged(); |
| 1001 } |
| 1002 |
| 1003 @Override |
| 1004 public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { |
| 1005 onScrollingStateChanged(); |
| 1006 } |
| 1007 |
| 1008 private void onScrollingStateChanged() { |
| 1009 FullscreenManager fullscreenManager = getFullscreenManager(); |
| 1010 if (fullscreenManager == null) return; |
| 1011 fullscreenManager.onContentViewScrollingStateChanged( |
| 1012 getContentViewCore() != null && getContentViewCore().isS
crollInProgress()); |
| 1013 } |
| 1014 }; |
| 1015 } |
| 1016 |
| 1017 @Override |
| 1018 protected void destroyContentViewCoreInternal(ContentViewCore cvc) { |
| 1019 super.destroyContentViewCoreInternal(cvc); |
| 1020 |
| 1021 if (mGestureStateListener != null) { |
| 1022 cvc.removeGestureStateListener(mGestureStateListener); |
| 1023 } |
| 1024 if (mWebContentsObserver != null) { |
| 1025 mWebContentsObserver.destroy(); |
| 1026 } |
| 1027 mWebContentsObserver = null; |
| 1028 } |
| 1029 |
| 1030 @Override |
| 1031 public void stopLoading() { |
| 1032 super.stopLoading(); |
| 1033 mBackgroundContentViewHelper.stopLoading(); |
| 1034 } |
| 1035 |
| 1036 @Override |
| 1037 public int getProgress() { |
| 1038 if (mBackgroundContentViewHelper.hasPendingBackgroundPage() |
| 1039 || mBackgroundContentViewHelper.isPageSwappingInProgress()) { |
| 1040 return mBackgroundContentViewHelper.getProgress(); |
| 1041 } |
| 1042 int currentTabProgress = super.getProgress(); |
| 1043 return Math.max(mLoadProgressAtViewSwapInTime, currentTabProgress); |
| 1044 } |
| 1045 |
| 1046 /** |
| 1047 * @return Whether the tab is ready to display or it should be faded in as i
t loads. |
| 1048 */ |
| 1049 public boolean shouldStall() { |
| 1050 return (isFrozen() || needsReload()) |
| 1051 && !NativePageFactory.isNativePageUrl(getUrl(), isIncognito()); |
| 1052 } |
| 1053 |
| 1054 @Override |
| 1055 protected void showInternal(TabSelectionType type) { |
| 1056 super.showInternal(type); |
| 1057 |
| 1058 // If the NativePage was frozen while in the background (see NativePageA
ssassin), |
| 1059 // recreate the NativePage now. |
| 1060 if (getNativePage() instanceof FrozenNativePage) { |
| 1061 maybeShowNativePage(getUrl(), true); |
| 1062 } |
| 1063 NativePageAssassin.getInstance().tabShown(this); |
| 1064 } |
| 1065 |
| 1066 @Override |
| 1067 protected void restoreIfNeededInternal() { |
| 1068 super.restoreIfNeededInternal(); |
| 1069 } |
| 1070 |
| 1071 @Override |
| 1072 protected void hideInternal() { |
| 1073 super.hideInternal(); |
| 1074 cancelEnableFullscreenLoadDelay(); |
| 1075 |
| 1076 // Allow this tab's NativePage to be frozen if it stays hidden for a whi
le. |
| 1077 NativePageAssassin.getInstance().tabHidden(this); |
| 1078 |
| 1079 mTabRedirectHandler.clear(); |
| 1080 } |
| 1081 |
| 1082 /** |
| 1083 * Checks if spdy proxy is enabled for input url. |
| 1084 * @param url Input url to check for spdy setting. |
| 1085 * @return true if url is enabled for spdy proxy. |
| 1086 */ |
| 1087 protected boolean isSpdyProxyEnabledForUrl(String url) { |
| 1088 if (DataReductionProxySettings.getInstance().isDataReductionProxyEnabled
() |
| 1089 && url != null && !url.toLowerCase(Locale.US).startsWith("https:
//") |
| 1090 && !isIncognito()) { |
| 1091 return true; |
| 1092 } |
| 1093 return false; |
| 1094 } |
| 1095 |
| 1096 @Override |
| 1097 public void goBack() { |
| 1098 mBackgroundContentViewHelper.recordBack(); |
| 1099 super.goBack(); |
| 1100 } |
| 1101 |
| 1102 @Override |
| 1103 public void reload() { |
| 1104 mBackgroundContentViewHelper.recordReload(); |
| 1105 super.reload(); |
| 1106 } |
| 1107 |
| 1108 @Override |
| 1109 public void reloadIgnoringCache() { |
| 1110 mBackgroundContentViewHelper.recordReload(); |
| 1111 super.reloadIgnoringCache(); |
| 1112 } |
| 1113 |
| 1114 @Override |
| 1115 public int loadUrl(LoadUrlParams params) { |
| 1116 try { |
| 1117 TraceEvent.begin("ChromeTab.loadUrl"); |
| 1118 |
| 1119 // The data reduction proxy can only be set to pass through mode via
loading an image in |
| 1120 // a new tab. We squirrel away whether pass through mode was set, an
d check it in: |
| 1121 // @see ChromeWebContentsDelegateAndroid#onLoadStopped() |
| 1122 mLastPageLoadHasSpdyProxyPassthroughHeaders = false; |
| 1123 if (TextUtils.equals(params.getVerbatimHeaders(), PAGESPEED_PASSTHRO
UGH_HEADER)) { |
| 1124 mLastPageLoadHasSpdyProxyPassthroughHeaders = true; |
| 1125 } |
| 1126 |
| 1127 // TODO(tedchoc): When showing the android NTP, delay the call to na
tiveLoadUrl until |
| 1128 // the android view has entirely rendered. |
| 1129 if (!mIsNativePageCommitPending) { |
| 1130 mIsNativePageCommitPending = maybeShowNativePage(params.getUrl()
, false); |
| 1131 } |
| 1132 |
| 1133 return super.loadUrl(params); |
| 1134 } finally { |
| 1135 TraceEvent.end("ChromeTab.loadUrl"); |
| 1136 } |
| 1137 } |
| 1138 |
| 1139 @VisibleForTesting |
| 1140 public AuthenticatorNavigationInterceptor getAuthenticatorHelper() { |
| 1141 return getInterceptNavigationDelegate().mAuthenticatorHelper; |
| 1142 } |
| 1143 |
| 1144 /** |
| 1145 * @return the TabRedirectHandler for the tab. |
| 1146 */ |
| 1147 public TabRedirectHandler getTabRedirectHandler() { |
| 1148 return mTabRedirectHandler; |
| 1149 } |
| 1150 |
| 1151 private class InterceptNavigationDelegateImpl implements InterceptNavigation
Delegate { |
| 1152 final ExternalNavigationHandler mExternalNavHandler; |
| 1153 final AuthenticatorNavigationInterceptor mAuthenticatorHelper; |
| 1154 |
| 1155 InterceptNavigationDelegateImpl() { |
| 1156 mExternalNavHandler = new ExternalNavigationHandler(mActivity); |
| 1157 |
| 1158 mAuthenticatorHelper = ((ChromeMobileApplication) getApplicationCont
ext()) |
| 1159 .createAuthenticatorNavigationInterceptor(ChromeTab.this); |
| 1160 } |
| 1161 |
| 1162 public boolean shouldIgnoreNewTab(String url, boolean incognito) { |
| 1163 if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthe
nticatorUrl(url)) { |
| 1164 return true; |
| 1165 } |
| 1166 |
| 1167 ExternalNavigationParams params = new ExternalNavigationParams.Build
er(url, incognito) |
| 1168 .setTab(ChromeTab.this) |
| 1169 .setOpenInNewTab(true) |
| 1170 .build(); |
| 1171 return mExternalNavHandler.shouldOverrideUrlLoading(params) |
| 1172 != ExternalNavigationHandler.OverrideUrlLoadingResult.NO_OVE
RRIDE; |
| 1173 } |
| 1174 |
| 1175 @Override |
| 1176 public boolean shouldIgnoreNavigation(NavigationParams navigationParams)
{ |
| 1177 final String url = navigationParams.url; |
| 1178 |
| 1179 if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthe
nticatorUrl(url)) { |
| 1180 return true; |
| 1181 } |
| 1182 |
| 1183 mTabRedirectHandler.updateNewUrlLoading(navigationParams.pageTransit
ionType, |
| 1184 navigationParams.isRedirect, |
| 1185 navigationParams.hasUserGesture || navigationParams.hasUserG
estureCarryover, |
| 1186 mActivity.getLastUserInteractionTime(), getLastCommittedEntr
yIndex()); |
| 1187 final boolean shouldCloseTab = shouldCloseContentsOnOverrideUrlLoadi
ngAndLaunchIntent(); |
| 1188 boolean isInitialTabLaunchInBackground = |
| 1189 getLaunchType() == TabLaunchType.FROM_LONGPRESS_BACKGROUND &
& shouldCloseTab; |
| 1190 // http://crbug.com/448977: If a new tab is closed by this overridin
g, we should open an |
| 1191 // Intent in a new tab when Chrome receives it again. |
| 1192 ExternalNavigationParams params = new ExternalNavigationParams.Build
er( |
| 1193 url, isIncognito(), getReferrerUrl(), navigationParams.pageT
ransitionType, |
| 1194 navigationParams.isRedirect) |
| 1195 .setTab(ChromeTab.this) |
| 1196 .setApplicationMustBeInForeground(true) |
| 1197 .setRedirectHandler(mTabRedirectHandler) |
| 1198 .setOpenInNewTab(shouldCloseTab) |
| 1199 .setIsBackgroundTabNavigation(isHidden() && !isInitialTabLau
nchInBackground) |
| 1200 .setIsMainFrame(navigationParams.isMainFrame) |
| 1201 .setNeedsToCloseTabAfterIncognitoDialog(shouldCloseTab |
| 1202 && navigationParams.isMainFrame) |
| 1203 .build(); |
| 1204 ExternalNavigationHandler.OverrideUrlLoadingResult result = |
| 1205 mExternalNavHandler.shouldOverrideUrlLoading(params); |
| 1206 mLastOverrideUrlLoadingResult = result; |
| 1207 switch (result) { |
| 1208 case OVERRIDE_WITH_EXTERNAL_INTENT: |
| 1209 assert mExternalNavHandler.canExternalAppHandleUrl(url); |
| 1210 if (navigationParams.isMainFrame) { |
| 1211 onOverrideUrlLoadingAndLaunchIntent(); |
| 1212 } |
| 1213 return true; |
| 1214 case OVERRIDE_WITH_CLOBBERING_TAB: |
| 1215 mShouldClearRedirectHistoryForTabClobbering = true; |
| 1216 return true; |
| 1217 case OVERRIDE_WITH_INCOGNITO_MODE: |
| 1218 if (!shouldCloseTab && navigationParams.isMainFrame) { |
| 1219 onOverrideUrlLoadingAndLaunchIntent(); |
| 1220 } |
| 1221 return true; |
| 1222 case NO_OVERRIDE: |
| 1223 default: |
| 1224 if (navigationParams.isExternalProtocol) { |
| 1225 logBlockedNavigationToDevToolsConsole(url); |
| 1226 return true; |
| 1227 } |
| 1228 return false; |
| 1229 } |
| 1230 } |
| 1231 |
| 1232 private void logBlockedNavigationToDevToolsConsole(String url) { |
| 1233 int resId = mExternalNavHandler.canExternalAppHandleUrl(url) |
| 1234 ? R.string.blocked_navigation_warning |
| 1235 : R.string.unreachable_navigation_warning; |
| 1236 getWebContents().addMessageToDevToolsConsole( |
| 1237 ConsoleMessageLevel.WARNING, getApplicationContext().getStri
ng(resId, url)); |
| 1238 } |
| 1239 |
| 1240 private String getReferrerUrl() { |
| 1241 // At this point, ContentView#getCurrentUrl() has already changed to |
| 1242 // be the URL to be loaded, so we need to check the last navigation
entry |
| 1243 // instead. Note that for browser-initiated navigations, we rely on
the |
| 1244 // mLastLoadUrlAddress check bailing early. |
| 1245 if (getWebContents() != null) { |
| 1246 return getWebContents().getNavigationController() |
| 1247 .getOriginalUrlForVisibleNavigationEntry(); |
| 1248 } else { |
| 1249 return null; |
| 1250 } |
| 1251 } |
| 1252 } |
| 1253 |
| 1254 @Override |
| 1255 protected boolean shouldIgnoreNewTab(String url, boolean incognito) { |
| 1256 InterceptNavigationDelegateImpl delegate = getInterceptNavigationDelegat
e(); |
| 1257 return delegate != null && delegate.shouldIgnoreNewTab(url, incognito); |
| 1258 } |
| 1259 |
| 1260 private int getLastCommittedEntryIndex() { |
| 1261 if (getWebContents() == null) return -1; |
| 1262 return getWebContents().getNavigationController().getLastCommittedEntryI
ndex(); |
| 1263 } |
| 1264 |
| 1265 /** |
| 1266 * @return A potential fallback texture id to use when trying to draw this t
ab. |
| 1267 */ |
| 1268 public int getFallbackTextureId() { |
| 1269 return INVALID_TAB_ID; |
| 1270 } |
| 1271 |
| 1272 private boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent() { |
| 1273 if (getWebContents() == null) return false; |
| 1274 if (!getWebContents().getNavigationController().canGoToOffset(0)) return
true; |
| 1275 |
| 1276 // http://crbug/415948 : if the last committed entry index which was sav
ed before this |
| 1277 // navigation is invalid, it means that this navigation is the first one
since this tab was |
| 1278 // created. |
| 1279 // In such case, we would like to close this tab. |
| 1280 if (mTabRedirectHandler.isOnNavigation()) { |
| 1281 return mTabRedirectHandler.getLastCommittedEntryIndexBeforeStartingN
avigation() |
| 1282 == TabRedirectHandler.INVALID_ENTRY_INDEX; |
| 1283 } |
| 1284 return false; |
| 1285 } |
| 1286 |
| 1287 /** |
| 1288 * Called when Chrome decides to override URL loading and show an intent pic
ker. |
| 1289 */ |
| 1290 protected void onOverrideUrlLoadingAndLaunchIntent() { |
| 1291 if (getWebContents() == null) return; |
| 1292 |
| 1293 // Before leaving Chrome, close the empty child tab. |
| 1294 // If a new tab is created through JavaScript open to load this |
| 1295 // url, we would like to close it as we will load this url in a |
| 1296 // different Activity. |
| 1297 if (shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent()) { |
| 1298 getChromeWebContentsDelegateAndroid().closeContents(); |
| 1299 } else if (mTabRedirectHandler.isOnNavigation()) { |
| 1300 int lastCommittedEntryIndexBeforeNavigation = |
| 1301 mTabRedirectHandler.getLastCommittedEntryIndexBeforeStarting
Navigation(); |
| 1302 if (getLastCommittedEntryIndex() > lastCommittedEntryIndexBeforeNavi
gation) { |
| 1303 // http://crbug/426679 : we want to go back to the last committe
d entry index which |
| 1304 // was saved before this navigation, and remove the empty entrie
s from the |
| 1305 // navigation history. |
| 1306 mClearAllForwardHistoryRequired = true; |
| 1307 getWebContents().getNavigationController().goToNavigationIndex( |
| 1308 lastCommittedEntryIndexBeforeNavigation); |
| 1309 } |
| 1310 } |
| 1311 } |
| 1312 |
| 1313 @Override |
| 1314 protected InterceptNavigationDelegateImpl getInterceptNavigationDelegate() { |
| 1315 return (InterceptNavigationDelegateImpl) super.getInterceptNavigationDel
egate(); |
| 1316 } |
| 1317 |
| 1318 @Override |
| 1319 public void setClosing(boolean closing) { |
| 1320 if (closing) mBackgroundContentViewHelper.recordTabClose(); |
| 1321 super.setClosing(closing); |
| 1322 } |
| 1323 |
| 1324 /** |
| 1325 * @return The reader mode manager for this tab that handles UI events for r
eader mode. |
| 1326 */ |
| 1327 public ReaderModeManager getReaderModeManager() { |
| 1328 return mReaderModeManager; |
| 1329 } |
| 1330 |
| 1331 public ReaderModeActivityDelegate getReaderModeActivityDelegate() { |
| 1332 if (!(mActivity instanceof CompositorChromeActivity)) return null; |
| 1333 return ((CompositorChromeActivity) mActivity).getReaderModeActivityDeleg
ate(); |
| 1334 } |
| 1335 |
| 1336 /** |
| 1337 * Shows a native page for url if it's a valid chrome-native URL. Otherwise,
does nothing. |
| 1338 * @param url The url of the current navigation. |
| 1339 * @param isReload Whether the current navigation is a reload. |
| 1340 * @return True, if a native page was displayed for url. |
| 1341 */ |
| 1342 private boolean maybeShowNativePage(String url, boolean isReload) { |
| 1343 NativePage candidateForReuse = isReload ? null : getNativePage(); |
| 1344 NativePage nativePage = NativePageFactory.createNativePageForURL(url, ca
ndidateForReuse, |
| 1345 this, mActivity.getTabModelSelector(), mActivity); |
| 1346 if (nativePage != null) { |
| 1347 showNativePage(nativePage); |
| 1348 notifyPageTitleChanged(); |
| 1349 notifyFaviconChanged(); |
| 1350 return true; |
| 1351 } |
| 1352 return false; |
| 1353 } |
| 1354 |
| 1355 // TODO(dtrainor): Port more methods to the observer. |
| 1356 private final TabObserver mTabObserver = new EmptyTabObserver() { |
| 1357 @Override |
| 1358 public void onContentChanged(Tab tab) { |
| 1359 mLoadProgressAtViewSwapInTime = 0; |
| 1360 } |
| 1361 |
| 1362 @Override |
| 1363 public void onSSLStateUpdated(Tab tab) { |
| 1364 PolicyAuditor auditor = |
| 1365 ((ChromeMobileApplication) getApplicationContext()).getPolic
yAuditor(); |
| 1366 auditor.notifyCertificateFailure(getWebContents(), getApplicationCon
text()); |
| 1367 updateFullscreenEnabledState(); |
| 1368 } |
| 1369 |
| 1370 @Override |
| 1371 public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean
didFinishLoad) { |
| 1372 if (!didStartLoad) return; |
| 1373 |
| 1374 String url = tab.getUrl(); |
| 1375 // Simulate the PAGE_LOAD_STARTED notification that we did not get. |
| 1376 didStartPageLoad(url, false); |
| 1377 if (didFinishLoad) { |
| 1378 // Simulate the PAGE_LOAD_FINISHED notification that we did not
get. |
| 1379 didFinishPageLoad(); |
| 1380 } |
| 1381 } |
| 1382 }; |
| 1383 |
| 1384 @VisibleForTesting |
| 1385 public OverrideUrlLoadingResult getLastOverrideUrlLoadingResultForTests() { |
| 1386 return mLastOverrideUrlLoadingResult; |
| 1387 } |
| 1388 } |
OLD | NEW |