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

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

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ChromeTab.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ChromeTab.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ChromeTab.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aca3d8a199e4050c46b8981c05bb0284cf9dc30
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/tab/ChromeTab.java
@@ -0,0 +1,1388 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.ActionMode;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.Log;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.ChromeMobileApplication;
+import org.chromium.chrome.browser.CompositorChromeActivity;
+import org.chromium.chrome.browser.EmptyTabObserver;
+import org.chromium.chrome.browser.FrozenNativePage;
+import org.chromium.chrome.browser.IntentHandler.TabOpenType;
+import org.chromium.chrome.browser.NativePage;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.TabObserver;
+import org.chromium.chrome.browser.TabState;
+import org.chromium.chrome.browser.TabUma;
+import org.chromium.chrome.browser.TabUma.TabCreationState;
+import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator;
+import org.chromium.chrome.browser.contextmenu.ContextMenuParams;
+import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchTabHelper;
+import org.chromium.chrome.browser.crash.MinidumpUploadService;
+import org.chromium.chrome.browser.dom_distiller.ReaderModeActivityDelegate;
+import org.chromium.chrome.browser.dom_distiller.ReaderModeManager;
+import org.chromium.chrome.browser.download.ChromeDownloadDelegate;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.OverrideUrlLoadingResult;
+import org.chromium.chrome.browser.externalnav.ExternalNavigationParams;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
+import org.chromium.chrome.browser.media.MediaNotificationService;
+import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
+import org.chromium.chrome.browser.ntp.NativePageAssassin;
+import org.chromium.chrome.browser.ntp.NativePageFactory;
+import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
+import org.chromium.chrome.browser.policy.PolicyAuditor;
+import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent;
+import org.chromium.chrome.browser.preferences.PrefServiceBridge;
+import org.chromium.chrome.browser.rlz.RevenueStats;
+import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.chrome.browser.tab.BackgroundContentViewHelper.BackgroundContentViewDelegate;
+import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
+import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
+import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
+import org.chromium.components.navigation_interception.NavigationParams;
+import org.chromium.content.browser.ActivityContentVideoViewClient;
+import org.chromium.content.browser.ContentVideoViewClient;
+import org.chromium.content.browser.ContentViewClient;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.SelectActionMode;
+import org.chromium.content.browser.SelectActionModeCallback.ActionHandler;
+import org.chromium.content.browser.crypto.CipherFactory;
+import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.content_public.browser.InvalidateTypes;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_public.browser.WebContentsObserver;
+import org.chromium.content_public.common.ConsoleMessageLevel;
+import org.chromium.content_public.common.Referrer;
+import org.chromium.ui.WindowOpenDisposition;
+import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.base.WindowAndroid;
+
+import java.util.Locale;
+
+/**
+ * A representation of a Tab for Chrome. This manages wrapping a WebContents and interacting with
+ * most of Chromium while representing a consistent version of web content to the front end.
+ */
+public class ChromeTab extends Tab {
+ public static final int NTP_TAB_ID = -2;
+
+ private static final String TAG = "ChromeTab";
+
+ // URL didFailLoad error code. Should match the value in net_error_list.h.
+ public static final int BLOCKED_BY_ADMINISTRATOR = -22;
+
+ public static final String PAGESPEED_PASSTHROUGH_HEADER =
+ "X-PSA-Client-Options: v=1,m=1\nCache-Control: no-cache";
+
+ private static final int MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD = 1;
+
+ /** The maximum amount of time to wait for a page to load before entering fullscreen. -1 means
+ * wait until the page finishes loading. */
+ private static final long MAX_FULLSCREEN_LOAD_DELAY_MS = 3000;
+
+ private ReaderModeManager mReaderModeManager;
+
+ private TabRedirectHandler mTabRedirectHandler;
+
+ private ChromeDownloadDelegate mDownloadDelegate;
+
+ private boolean mIsFullscreenWaitingForLoad = false;
+ private ExternalNavigationHandler.OverrideUrlLoadingResult mLastOverrideUrlLoadingResult =
+ ExternalNavigationHandler.OverrideUrlLoadingResult.NO_OVERRIDE;
+
+ /**
+ * Whether didCommitProvisionalLoadForFrame() hasn't yet been called for the current native page
+ * (page A). To decrease latency, we show native pages in both loadUrl() and
+ * didCommitProvisionalLoadForFrame(). However, we mustn't show a new native page (page B) in
+ * loadUrl() if the current native page hasn't yet been committed. Otherwise, we'll show each
+ * page twice (A, B, A, B): the first two times in loadUrl(), the second two times in
+ * didCommitProvisionalLoadForFrame().
+ */
+ private boolean mIsNativePageCommitPending;
+
+ protected final ChromeActivity mActivity;
+
+ private WebContentsObserver mWebContentsObserver;
+
+ private Handler mHandler;
+
+ private final Runnable mCloseContentsRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getTabModelSelector().closeTab(ChromeTab.this);
+ }
+ };
+
+ /**
+ * The data reduction proxy was in use on the last page load if true.
+ */
+ protected boolean mUsedSpdyProxy;
+
+ /**
+ * The data reduction proxy was in pass through mode on the last page load if true.
+ */
+ protected boolean mUsedSpdyProxyWithPassthrough;
+
+ /**
+ * The last page load had request headers indicating that the data reduction proxy should
+ * be put in pass through mode, if true.
+ */
+ private boolean mLastPageLoadHasSpdyProxyPassthroughHeaders;
+
+ /**
+ * Listens to gesture events fired by the ContentViewCore.
+ */
+ private GestureStateListener mGestureStateListener;
+
+ /**
+ * The background content view helper which loads the original page in background content view.
+ */
+ private BackgroundContentViewHelper mBackgroundContentViewHelper;
+
+ /**
+ * The load progress of swapped in content view at the time of swap.
+ */
+ private int mLoadProgressAtViewSwapInTime;
+
+ /**
+ * Whether forward history should be cleared after navigation is committed.
+ */
+ private boolean mClearAllForwardHistoryRequired;
+
+ private boolean mShouldClearRedirectHistoryForTabClobbering;
+
+ /**
+ * Basic constructor. This is hidden, so that explicitly named factory methods are used to
+ * create tabs. initialize() needs to be called afterwards to complete the second level
+ * initialization.
+ * @param creationState State in which the tab is created, needed to initialize TabUma
+ * accounting. When null, TabUma will not be initialized.
+ * @param frozenState TabState that was saved when the Tab was last persisted to storage.
+ */
+ protected ChromeTab(
+ int id, ChromeActivity activity, boolean incognito, WindowAndroid nativeWindow,
+ TabLaunchType type, int parentId, TabCreationState creationState,
+ TabState frozenState) {
+ super(id, parentId, incognito, activity, nativeWindow, type, frozenState);
+
+ if (frozenState == null) {
+ assert type != TabLaunchType.FROM_RESTORE
+ && creationState != TabCreationState.FROZEN_ON_RESTORE;
+ } else {
+ assert type == TabLaunchType.FROM_RESTORE
+ && creationState == TabCreationState.FROZEN_ON_RESTORE;
+ }
+
+ addObserver(mTabObserver);
+ mActivity = activity;
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg == null) return;
+ if (msg.what == MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD) {
+ enableFullscreenAfterLoad();
+ }
+ }
+ };
+ setContentViewClient(createContentViewClient());
+ if (mActivity != null && creationState != null) {
+ setTabUma(new TabUma(
+ this, creationState, mActivity.getTabModelSelector().getModel(incognito)));
+ }
+
+ if (incognito) {
+ CipherFactory.getInstance().triggerKeyGeneration();
+ }
+
+ mReaderModeManager = new ReaderModeManager(this, activity);
+ RevenueStats.getInstance().tabCreated(this);
+
+ mTabRedirectHandler = new TabRedirectHandler(activity);
+
+ ContextualSearchTabHelper.createForTab(this);
+ if (nativeWindow != null) ThumbnailTabHelper.createForTab(this);
+ }
+
+ /**
+ * Creates a minimal {@link ChromeTab} for testing. Do not use outside testing.
+ *
+ * @param id The id of the tab.
+ * @param incognito Whether the tab is incognito.
+ */
+ @VisibleForTesting
+ public ChromeTab(int id, boolean incognito) {
+ super(id, incognito, null, null);
+ mActivity = null;
+ mTabRedirectHandler = new TabRedirectHandler(null);
+ }
+
+ /**
+ * Creates a fresh tab. initialize() needs to be called afterwards to complete the second level
+ * initialization.
+ * @param initiallyHidden true iff the tab being created is initially in background
+ */
+ public static ChromeTab createLiveTab(int id, ChromeActivity activity, boolean incognito,
+ WindowAndroid nativeWindow, TabLaunchType type, int parentId, boolean initiallyHidden) {
+ return new ChromeTab(id, activity, incognito, nativeWindow, type, parentId,
+ initiallyHidden ? TabCreationState.LIVE_IN_BACKGROUND :
+ TabCreationState.LIVE_IN_FOREGROUND, null);
+ }
+
+ /**
+ * Creates a new, "frozen" tab from a saved state. This can be used for background tabs restored
+ * on cold start that should be loaded when switched to. initialize() needs to be called
+ * afterwards to complete the second level initialization.
+ */
+ public static ChromeTab createFrozenTabFromState(
+ int id, ChromeActivity activity, boolean incognito,
+ WindowAndroid nativeWindow, int parentId, TabState state) {
+ assert state != null;
+ return new ChromeTab(id, activity, incognito, nativeWindow,
+ TabLaunchType.FROM_RESTORE, parentId, TabCreationState.FROZEN_ON_RESTORE,
+ state);
+ }
+
+ /**
+ * Creates a new tab to be loaded lazily. This can be used for tabs opened in the background
+ * that should be loaded when switched to. initialize() needs to be called afterwards to
+ * complete the second level initialization.
+ */
+ public static ChromeTab createTabForLazyLoad(ChromeActivity activity, boolean incognito,
+ WindowAndroid nativeWindow, TabLaunchType type, int parentId,
+ LoadUrlParams loadUrlParams) {
+ ChromeTab tab = new ChromeTab(
+ INVALID_TAB_ID, activity, incognito, nativeWindow, type, parentId,
+ TabCreationState.FROZEN_FOR_LAZY_LOAD, null);
+ tab.setPendingLoadParams(loadUrlParams);
+ return tab;
+ }
+
+ public static ChromeTab fromTab(Tab tab) {
+ return (ChromeTab) tab;
+ }
+
+ /**
+ * Initializes the ChromeTab after construction with an existing ContentViewCore.
+ */
+ @Override
+ protected void internalInit() {
+ super.internalInit();
+ if (mBackgroundContentViewHelper == null) {
+ BackgroundContentViewDelegate delegate = new BackgroundContentViewDelegate() {
+ @Override
+ public void onBackgroundViewReady(
+ ContentViewCore cvc, boolean didStartLoad, boolean didFinishLoad,
+ int progress) {
+ WebContents previewWebContents = getWebContents();
+ swapContentViewCore(cvc, false, didStartLoad, didFinishLoad);
+ mLoadProgressAtViewSwapInTime = progress;
+ mBackgroundContentViewHelper.unloadAndDeleteWebContents(previewWebContents);
+
+ // Enter to fullscreen.
+ mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_LOAD_DELAY_MS);
+ updateFullscreenEnabledState();
+ }
+
+ @Override
+ public void onLoadProgressChanged(int progress) {
+ notifyLoadProgress(getProgress());
+ }
+ };
+ mBackgroundContentViewHelper = new BackgroundContentViewHelper(
+ getWindowAndroid(), this, delegate);
+ }
+ }
+
+ /**
+ * Remember if the last load used the data reduction proxy, and if so,
+ * also remember if it used pass through mode.
+ */
+ private void maybeSetDataReductionProxyUsed() {
+ // Ignore internal URLs.
+ String url = getUrl();
+ if (url != null && url.toLowerCase(Locale.US).startsWith("chrome://")) {
+ return;
+ }
+ mUsedSpdyProxy = false;
+ mUsedSpdyProxyWithPassthrough = false;
+ if (isSpdyProxyEnabledForUrl(url)) {
+ mUsedSpdyProxy = true;
+ if (mLastPageLoadHasSpdyProxyPassthroughHeaders) {
+ mLastPageLoadHasSpdyProxyPassthroughHeaders = false;
+ mUsedSpdyProxyWithPassthrough = true;
+ }
+ }
+ }
+
+ @Override
+ protected void openNewTab(
+ LoadUrlParams params, TabLaunchType launchType, Tab parentTab, boolean incognito) {
+ mActivity.getTabModelSelector().openNewTab(params, launchType, parentTab, incognito);
+ }
+
+ @Override
+ protected TabChromeWebContentsDelegateAndroid createWebContentsDelegate() {
+ return new TabChromeWebContentsDelegateAndroidImpl();
+ }
+
+ /**
+ * An implementation for this tab's web contents delegate.
+ */
+ public class TabChromeWebContentsDelegateAndroidImpl
+ extends TabChromeWebContentsDelegateAndroid {
+ /**
+ * This method is meant to be overridden by DocumentTab because the
+ * TabModelSelector returned by the activity is not correct.
+ * TODO(dfalcantara): remove this when DocumentActivity.getTabModelSelector()
+ * will return the right TabModelSelector.
+ */
+ protected TabModel getTabModel() {
+ return mActivity.getTabModelSelector().getModel(isIncognito());
+ }
+
+ @Override
+ public boolean addNewContents(WebContents sourceWebContents, WebContents webContents,
+ int disposition, Rect initialPosition, boolean userGesture) {
+ if (isClosing()) return false;
+
+ // TODO(johnme): Open tabs in same order as Chrome.
+ Tab tab = mActivity.getTabCreator(isIncognito()).createTabWithWebContents(
+ webContents, getId(), TabLaunchType.FROM_LONGPRESS_FOREGROUND);
+
+ if (tab == null) return false;
+
+ if (disposition == WindowOpenDisposition.NEW_POPUP) {
+ PolicyAuditor auditor =
+ ((ChromeMobileApplication) getApplicationContext()).getPolicyAuditor();
+ auditor.notifyAuditEvent(getApplicationContext(), AuditEvent.OPEN_POPUP_URL_SUCCESS,
+ tab.getUrl(), "");
+ }
+
+ return true;
+ }
+
+ @Override
+ public void activateContents() {
+ boolean activityIsDestroyed = false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ activityIsDestroyed = mActivity.isDestroyed();
+ }
+ if (activityIsDestroyed || !isInitialized()) {
+ Log.e(TAG, "Activity destroyed before calling activateContents(). Bailing out.");
+ return;
+ }
+
+ TabModel model = getTabModel();
+ int index = model.indexOf(ChromeTab.this);
+ if (index == TabModel.INVALID_TAB_INDEX) return;
+
+ TabModelUtils.setIndex(model, index);
+
+ // This intent is sent in order to get the activity back to the foreground if it was
+ // not already. The previous call will activate the right tab in the context of the
+ // TabModel but will only show the tab to the user if Chrome was already in the
+ // foreground.
+ // The intent is getting the tabId mostly because it does not cost much to do so.
+ // When receiving the intent, the tab associated with the tabId should already be
+ // active.
+ // Note that calling only the intent in order to activate the tab is slightly slower
+ // because it will change the tab when the intent is handled, which happens after
+ // Chrome gets back to the foreground.
+ Intent newIntent = new Intent();
+ newIntent.setAction(Intent.ACTION_MAIN);
+ newIntent.setPackage(mActivity.getPackageName());
+ newIntent.putExtra(TabOpenType.BRING_TAB_TO_FRONT.name(),
+ ChromeTab.this.getId());
+
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ getApplicationContext().startActivity(newIntent);
+ }
+
+ @Override
+ public void navigationStateChanged(int flags) {
+ if ((flags & InvalidateTypes.TAB) != 0) {
+ MediaNotificationService.updateMediaNotificationForTab(
+ getApplicationContext(), getId(), isCapturingAudio(),
+ isCapturingVideo(), hasAudibleAudio(), getUrl());
+ }
+ super.navigationStateChanged(flags);
+ }
+
+ @Override
+ public void onLoadProgressChanged(int progress) {
+ if (!isLoading()) return;
+ if (progress >= mLoadProgressAtViewSwapInTime) mLoadProgressAtViewSwapInTime = 0;
+ notifyLoadProgress(getProgress());
+ }
+
+ @Override
+ public void closeContents() {
+ // Execute outside of callback, otherwise we end up deleting the native
+ // objects in the middle of executing methods on them.
+ mHandler.removeCallbacks(mCloseContentsRunnable);
+ mHandler.post(mCloseContentsRunnable);
+ }
+
+ @Override
+ public boolean takeFocus(boolean reverse) {
+ if (reverse) {
+ View menuButton = mActivity.findViewById(R.id.menu_button);
+ if (menuButton == null || !menuButton.isShown()) {
+ menuButton = mActivity.findViewById(R.id.document_menu_button);
+ }
+ if (menuButton != null && menuButton.isShown()) {
+ return menuButton.requestFocus();
+ }
+
+ View tabSwitcherButton = mActivity.findViewById(R.id.tab_switcher_button);
+ if (tabSwitcherButton != null && tabSwitcherButton.isShown()) {
+ return tabSwitcherButton.requestFocus();
+ }
+ } else {
+ View urlBar = mActivity.findViewById(R.id.url_bar);
+ if (urlBar != null) return urlBar.requestFocus();
+ }
+ return false;
+ }
+
+ @Override
+ public void handleKeyboardEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (mActivity.onKeyDown(event.getKeyCode(), event)) return;
+
+ // Handle the Escape key here (instead of in KeyboardShortcuts.java), so it doesn't
+ // interfere with other parts of the activity (e.g. the URL bar).
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
+ WebContents wc = getWebContents();
+ if (wc != null) wc.stop();
+ return;
+ }
+ }
+ handleMediaKey(event);
+ }
+
+ /**
+ * Redispatches unhandled media keys. This allows bluetooth headphones with play/pause or
+ * other buttons to function correctly.
+ */
+ @TargetApi(19)
+ private void handleMediaKey(KeyEvent e) {
+ if (Build.VERSION.SDK_INT < 19) return;
+ switch (e.getKeyCode()) {
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_CLOSE:
+ case KeyEvent.KEYCODE_MEDIA_EJECT:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
+ AudioManager am = (AudioManager) mActivity.getSystemService(
+ Context.AUDIO_SERVICE);
+ am.dispatchMediaKeyEvent(e);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * @return Whether audio is being captured.
+ */
+ private boolean isCapturingAudio() {
+ return !isClosing() && super.nativeIsCapturingAudio(getWebContents());
+ }
+
+ /**
+ * @return Whether video is being captured.
+ */
+ private boolean isCapturingVideo() {
+ return !isClosing() && super.nativeIsCapturingVideo(getWebContents());
+ }
+
+ /**
+ * @return Whether audio is being played.
+ */
+ private boolean hasAudibleAudio() {
+ return !isClosing() && super.nativeHasAudibleAudio(getWebContents());
+ }
+
+ }
+
+ /**
+ * Check whether the context menu download should be intercepted.
+ *
+ * @param url URL to be downloaded.
+ * @return whether the download should be intercepted.
+ */
+ protected boolean shouldInterceptContextMenuDownload(String url) {
+ return mDownloadDelegate.shouldInterceptContextMenuDownload(url);
+ }
+
+ private class ChromeTabChromeContextMenuItemDelegate extends TabChromeContextMenuItemDelegate {
+ private boolean mIsImage;
+ private boolean mIsVideo;
+
+ public void setParamsInfo(boolean isImage, boolean isVideo) {
+ mIsImage = isImage;
+ mIsVideo = isVideo;
+ }
+
+ @Override
+ public boolean isIncognitoSupported() {
+ return PrefServiceBridge.getInstance().isIncognitoModeEnabled();
+ }
+
+ @Override
+ public boolean canLoadOriginalImage() {
+ return mUsedSpdyProxy && !mUsedSpdyProxyWithPassthrough;
+ }
+
+ @Override
+ public boolean startDownload(String url, boolean isLink) {
+ if (isLink) {
+ RecordUserAction.record("MobileContextMenuDownloadLink");
+ if (shouldInterceptContextMenuDownload(url)) {
+ return false;
+ }
+ } else if (mIsImage) {
+ RecordUserAction.record("MobileContextMenuDownloadImage");
+ } else if (mIsVideo) {
+ RecordUserAction.record("MobileContextMenuDownloadVideo");
+ }
+ return true;
+ }
+
+ @Override
+ public void onSaveToClipboard(String text, boolean isUrl) {
+ if (isUrl) {
+ RecordUserAction.record("MobileContextMenuCopyLinkAddress");
+ } else {
+ RecordUserAction.record("MobileContextMenuCopyLinkText");
+ }
+ super.onSaveToClipboard(text, isUrl);
+ }
+
+ @Override
+ public void onSaveImageToClipboard(String url) {
+ RecordUserAction.record("MobileContextMenuSaveImage");
+ super.onSaveImageToClipboard(url);
+ }
+
+ @Override
+ public void onOpenInNewTab(String url, Referrer referrer) {
+ RecordUserAction.record("MobileContextMenuOpenLinkInNewTab");
+ RecordUserAction.record("MobileNewTabOpened");
+ LoadUrlParams loadUrlParams = new LoadUrlParams(url);
+ loadUrlParams.setReferrer(referrer);
+ mActivity.getTabModelSelector().openNewTab(loadUrlParams,
+ TabLaunchType.FROM_LONGPRESS_BACKGROUND, ChromeTab.this, isIncognito());
+ }
+
+ @Override
+ public void onOpenInNewIncognitoTab(String url) {
+ RecordUserAction.record("MobileContextMenuOpenLinkInIncognito");
+ RecordUserAction.record("MobileNewTabOpened");
+ mActivity.getTabModelSelector().openNewTab(new LoadUrlParams(url),
+ TabLaunchType.FROM_LONGPRESS_FOREGROUND, ChromeTab.this, true);
+ }
+
+ @Override
+ public void onOpenImageUrl(String url, Referrer referrer) {
+ RecordUserAction.record("MobileContextMenuViewImage");
+ super.onOpenImageUrl(url, referrer);
+ }
+
+ @Override
+ public void onOpenImageInNewTab(String url, Referrer referrer) {
+ boolean useOriginal = isSpdyProxyEnabledForUrl(url);
+ RecordUserAction.record("MobileContextMenuOpenImageInNewTab");
+ if (useOriginal) {
+ RecordUserAction.record("MobileContextMenuOpenOriginalImageInNewTab");
+ }
+
+ LoadUrlParams loadUrlParams = new LoadUrlParams(url);
+ loadUrlParams.setVerbatimHeaders(useOriginal ? PAGESPEED_PASSTHROUGH_HEADER : null);
+ loadUrlParams.setReferrer(referrer);
+ mActivity.getTabModelSelector().openNewTab(loadUrlParams,
+ TabLaunchType.FROM_LONGPRESS_BACKGROUND, ChromeTab.this, isIncognito());
+ }
+
+ @Override
+ public void onSearchByImageInNewTab() {
+ RecordUserAction.record("MobileContextMenuSearchByImage");
+ super.onSearchByImageInNewTab();
+ }
+ }
+
+ /**
+ * This class is solely to track UMA stats. When we upstream UMA stats we can remove this.
+ */
+ private static class ChromeTabChromeContextMenuPopulator extends ChromeContextMenuPopulator {
+ private final ChromeTabChromeContextMenuItemDelegate mDelegate;
+
+ public ChromeTabChromeContextMenuPopulator(
+ ChromeTabChromeContextMenuItemDelegate delegate) {
+ super(delegate);
+
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void buildContextMenu(ContextMenu menu, Context context,
+ ContextMenuParams params) {
+ if (params.isAnchor()) {
+ RecordUserAction.record("MobileContextMenuLink");
+ } else if (params.isImage()) {
+ RecordUserAction.record("MobileContextMenuImage");
+ } else if (params.isSelectedText()) {
+ RecordUserAction.record("MobileContextMenuText");
+ } else if (params.isVideo()) {
+ RecordUserAction.record("MobileContextMenuVideo");
+ }
+
+ mDelegate.setParamsInfo(params.isImage(), params.isVideo());
+ super.buildContextMenu(menu, context, params);
+ }
+ }
+
+ @Override
+ protected ContextMenuPopulator createContextMenuPopulator() {
+ return new ChromeTabChromeContextMenuPopulator(
+ new ChromeTabChromeContextMenuItemDelegate());
+ }
+
+ /** @return The {@link BackgroundContentViewHelper} associated with the current tab. */
+ public BackgroundContentViewHelper getBackgroundContentViewHelper() {
+ return mBackgroundContentViewHelper;
+ }
+
+ @VisibleForTesting
+ public void setViewClientForTesting(ContentViewClient client) {
+ setContentViewClient(client);
+ }
+
+ @VisibleForTesting
+ public ContentViewClient getViewClientForTesting() {
+ return getContentViewClient();
+ }
+
+ private ContentViewClient createContentViewClient() {
+ return new TabContentViewClient() {
+ @Override
+ public void onBackgroundColorChanged(int color) {
+ ChromeTab.this.onBackgroundColorChanged(color);
+ }
+
+ @Override
+ public void onOffsetsForFullscreenChanged(
+ float topControlsOffsetY, float contentOffsetY, float overdrawBottomHeight) {
+ onOffsetsChanged(topControlsOffsetY, contentOffsetY, overdrawBottomHeight,
+ isShowingSadTab());
+ }
+
+ @Override
+ public void performWebSearch(String searchQuery) {
+ if (TextUtils.isEmpty(searchQuery)) return;
+ String url = TemplateUrlService.getInstance().getUrlForSearchQuery(searchQuery);
+ String headers = GeolocationHeader.getGeoHeader(getApplicationContext(), url,
+ isIncognito());
+
+ LoadUrlParams loadUrlParams = new LoadUrlParams(url);
+ loadUrlParams.setVerbatimHeaders(headers);
+ loadUrlParams.setTransitionType(PageTransition.GENERATED);
+ mActivity.getTabModelSelector().openNewTab(loadUrlParams,
+ TabLaunchType.FROM_LONGPRESS_FOREGROUND, ChromeTab.this, isIncognito());
+ }
+
+ @Override
+ public boolean doesPerformWebSearch() {
+ return true;
+ }
+
+ @Override
+ public SelectActionMode startActionMode(
+ View view, ActionHandler actionHandler, boolean floating) {
+ if (floating) return null;
+ ChromeSelectActionModeCallback callback =
+ new ChromeSelectActionModeCallback(view.getContext(), actionHandler);
+ ActionMode actionMode = view.startActionMode(callback);
+ return actionMode != null ? new SelectActionMode(actionMode) : null;
+ }
+
+ @Override
+ public boolean supportsFloatingActionMode() {
+ return false;
+ }
+
+ @Override
+ public ContentVideoViewClient getContentVideoViewClient() {
+ return new ActivityContentVideoViewClient(mActivity) {
+ @Override
+ public void enterFullscreenVideo(View view) {
+ super.enterFullscreenVideo(view);
+ FullscreenManager fullscreenManager = getFullscreenManager();
+ if (fullscreenManager != null) {
+ fullscreenManager.setOverlayVideoMode(true);
+ // Disable double tap for video.
+ if (getContentViewCore() != null) {
+ getContentViewCore().updateDoubleTapSupport(false);
+ }
+ }
+ }
+
+ @Override
+ public void exitFullscreenVideo() {
+ FullscreenManager fullscreenManager = getFullscreenManager();
+ if (fullscreenManager != null) {
+ fullscreenManager.setOverlayVideoMode(false);
+ // Disable double tap for video.
+ if (getContentViewCore() != null) {
+ getContentViewCore().updateDoubleTapSupport(true);
+ }
+ }
+ super.exitFullscreenVideo();
+ }
+ };
+ }
+ };
+ }
+
+ private WebContentsObserver createWebContentsObserver(WebContents webContents) {
+ return new WebContentsObserver(webContents) {
+ @Override
+ public void didFinishLoad(long frameId, String validatedUrl, boolean isMainFrame) {
+ PolicyAuditor auditor =
+ ((ChromeMobileApplication) getApplicationContext()).getPolicyAuditor();
+ auditor.notifyAuditEvent(
+ getApplicationContext(), AuditEvent.OPEN_URL_SUCCESS, validatedUrl, "");
+ }
+
+ @Override
+ public void didFailLoad(boolean isProvisionalLoad,
+ boolean isMainFrame, int errorCode, String description, String failingUrl) {
+ PolicyAuditor auditor =
+ ((ChromeMobileApplication) getApplicationContext()).getPolicyAuditor();
+ auditor.notifyAuditEvent(getApplicationContext(), AuditEvent.OPEN_URL_FAILURE,
+ failingUrl, description);
+ if (errorCode == BLOCKED_BY_ADMINISTRATOR) {
+ auditor.notifyAuditEvent(
+ getApplicationContext(), AuditEvent.OPEN_URL_BLOCKED, failingUrl, "");
+ }
+ }
+
+ @Override
+ public void didCommitProvisionalLoadForFrame(
+ long frameId, boolean isMainFrame, String url, int transitionType) {
+ if (!isMainFrame) return;
+
+ mIsNativePageCommitPending = false;
+ boolean isReload = (transitionType == PageTransition.RELOAD);
+ if (!maybeShowNativePage(url, isReload)) {
+ showRenderedPage();
+ }
+
+ if (!mBackgroundContentViewHelper.hasPendingBackgroundPage()) {
+ mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD);
+ mHandler.sendEmptyMessageDelayed(
+ MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_LOAD_DELAY_MS);
+ updateFullscreenEnabledState();
+ }
+
+ // http://crbug/426679 : if navigation is canceled due to intent handling, we want
+ // to go back to the last committed entry index which was saved before the
+ // navigation, and remove the empty entries from the navigation history.
+ if (mClearAllForwardHistoryRequired && getWebContents() != null) {
+ NavigationController navigationController =
+ getWebContents().getNavigationController();
+ int lastCommittedEntryIndex = getLastCommittedEntryIndex();
+ while (navigationController.canGoForward()) {
+ boolean ret = navigationController.removeEntryAtIndex(
+ lastCommittedEntryIndex + 1);
+ assert ret;
+ }
+ } else if (mShouldClearRedirectHistoryForTabClobbering
+ && getWebContents() != null) {
+ // http://crbug/479056: Even if we clobber the current tab, we want to remove
+ // redirect history to be consistent.
+ NavigationController navigationController =
+ getWebContents().getNavigationController();
+ int indexBeforeRedirection = mTabRedirectHandler
+ .getLastCommittedEntryIndexBeforeStartingNavigation();
+ int lastCommittedEntryIndex = getLastCommittedEntryIndex();
+ for (int i = lastCommittedEntryIndex - 1; i > indexBeforeRedirection; --i) {
+ boolean ret = navigationController.removeEntryAtIndex(i);
+ assert ret;
+ }
+ }
+ mClearAllForwardHistoryRequired = false;
+ mShouldClearRedirectHistoryForTabClobbering = false;
+ }
+
+ @Override
+ public void didAttachInterstitialPage() {
+ PolicyAuditor auditor =
+ ((ChromeMobileApplication) getApplicationContext()).getPolicyAuditor();
+ auditor.notifyCertificateFailure(getWebContents(), getApplicationContext());
+ }
+
+ @Override
+ public void didDetachInterstitialPage() {
+ if (!maybeShowNativePage(getUrl(), false)) {
+ showRenderedPage();
+ }
+ }
+
+ @Override
+ public void destroy() {
+ MediaNotificationService.updateMediaNotificationForTab(
+ getApplicationContext(), getId(), false, false, false, getUrl());
+ super.destroy();
+ }
+ };
+ }
+
+ @Override
+ protected void didStartPageLoad(String validatedUrl, boolean showingErrorPage) {
+ if (mBackgroundContentViewHelper.isPageSwappingInProgress()) {
+ mLoadProgressAtViewSwapInTime = 0;
+ }
+
+ mIsFullscreenWaitingForLoad = !DomDistillerUrlUtils.isDistilledPage(validatedUrl);
+ mLoadProgressAtViewSwapInTime = 0;
+
+ super.didStartPageLoad(validatedUrl, showingErrorPage);
+ }
+
+ @Override
+ protected void didFinishPageLoad() {
+ // We should not mark finished if we have pending background page to swap in.
+ // We'll instead call didFinishPageLoad after the background page is swapped in.
+ if (mBackgroundContentViewHelper.hasPendingBackgroundPage()) return;
+
+ mLoadProgressAtViewSwapInTime = 0;
+
+ super.didFinishPageLoad();
+
+ // Handle the case where we were pre-renderered and the enable fullscreen message was
+ // never enqueued.
+ if (mIsFullscreenWaitingForLoad
+ && !mHandler.hasMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD)
+ && !mBackgroundContentViewHelper.hasPendingBackgroundPage()) {
+ mHandler.sendEmptyMessageDelayed(
+ MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD, MAX_FULLSCREEN_LOAD_DELAY_MS);
+ }
+
+ maybeSetDataReductionProxyUsed();
+ }
+
+ @Override
+ protected void didFailPageLoad(int errorCode) {
+ mLoadProgressAtViewSwapInTime = 0;
+ cancelEnableFullscreenLoadDelay();
+ super.didFailPageLoad(errorCode);
+ updateFullscreenEnabledState();
+ }
+
+ private void cancelEnableFullscreenLoadDelay() {
+ mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD);
+ mIsFullscreenWaitingForLoad = false;
+ }
+
+ /**
+ * Removes the enable fullscreen runnable from the UI queue and runs it immediately.
+ */
+ @VisibleForTesting
+ public void processEnableFullscreenRunnableForTest() {
+ if (mHandler.hasMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD)) {
+ mHandler.removeMessages(MSG_ID_ENABLE_FULLSCREEN_AFTER_LOAD);
+ enableFullscreenAfterLoad();
+ }
+ }
+
+ private void enableFullscreenAfterLoad() {
+ if (!mIsFullscreenWaitingForLoad) return;
+
+ mIsFullscreenWaitingForLoad = false;
+ updateFullscreenEnabledState();
+ }
+
+ @Override
+ protected boolean isHidingTopControlsEnabled() {
+ return super.isHidingTopControlsEnabled() && !mIsFullscreenWaitingForLoad;
+ }
+
+ @Override
+ protected void handleTabCrash() {
+ super.handleTabCrash();
+
+ // Update the most recent minidump file with the logcat. Doing this asynchronously
+ // adds a race condition in the case of multiple simultaneously renderer crashses
+ // but because the data will be the same for all of them it is innocuous. We can
+ // attempt to do this regardless of whether it was a foreground tab in the event
+ // that it's a real crash and not just android killing the tab.
+ Context context = getApplicationContext();
+ Intent intent = MinidumpUploadService.createFindAndUploadLastCrashIntent(context);
+ context.startService(intent);
+ RecordUserAction.record("MobileBreakpadUploadAttempt");
+ }
+
+ @Override
+ protected void setContentViewCore(ContentViewCore cvc) {
+ try {
+ TraceEvent.begin("ChromeTab.setContentViewCore");
+ super.setContentViewCore(cvc);
+ mWebContentsObserver = createWebContentsObserver(cvc.getWebContents());
+
+ mDownloadDelegate = new ChromeDownloadDelegate(mActivity,
+ mActivity.getTabModelSelector(), this);
+ cvc.setDownloadDelegate(mDownloadDelegate);
+ setInterceptNavigationDelegate(new InterceptNavigationDelegateImpl());
+
+ if (mGestureStateListener == null) mGestureStateListener = createGestureStateListener();
+ cvc.addGestureStateListener(mGestureStateListener);
+ } finally {
+ TraceEvent.end("ChromeTab.setContentViewCore");
+ }
+ }
+
+ private GestureStateListener createGestureStateListener() {
+ return new GestureStateListener() {
+ @Override
+ public void onFlingStartGesture(int vx, int vy, int scrollOffsetY, int scrollExtentY) {
+ onScrollingStateChanged();
+ }
+
+ @Override
+ public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
+ onScrollingStateChanged();
+ }
+
+ @Override
+ public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
+ onScrollingStateChanged();
+ }
+
+ @Override
+ public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
+ onScrollingStateChanged();
+ }
+
+ private void onScrollingStateChanged() {
+ FullscreenManager fullscreenManager = getFullscreenManager();
+ if (fullscreenManager == null) return;
+ fullscreenManager.onContentViewScrollingStateChanged(
+ getContentViewCore() != null && getContentViewCore().isScrollInProgress());
+ }
+ };
+ }
+
+ @Override
+ protected void destroyContentViewCoreInternal(ContentViewCore cvc) {
+ super.destroyContentViewCoreInternal(cvc);
+
+ if (mGestureStateListener != null) {
+ cvc.removeGestureStateListener(mGestureStateListener);
+ }
+ if (mWebContentsObserver != null) {
+ mWebContentsObserver.destroy();
+ }
+ mWebContentsObserver = null;
+ }
+
+ @Override
+ public void stopLoading() {
+ super.stopLoading();
+ mBackgroundContentViewHelper.stopLoading();
+ }
+
+ @Override
+ public int getProgress() {
+ if (mBackgroundContentViewHelper.hasPendingBackgroundPage()
+ || mBackgroundContentViewHelper.isPageSwappingInProgress()) {
+ return mBackgroundContentViewHelper.getProgress();
+ }
+ int currentTabProgress = super.getProgress();
+ return Math.max(mLoadProgressAtViewSwapInTime, currentTabProgress);
+ }
+
+ /**
+ * @return Whether the tab is ready to display or it should be faded in as it loads.
+ */
+ public boolean shouldStall() {
+ return (isFrozen() || needsReload())
+ && !NativePageFactory.isNativePageUrl(getUrl(), isIncognito());
+ }
+
+ @Override
+ protected void showInternal(TabSelectionType type) {
+ super.showInternal(type);
+
+ // If the NativePage was frozen while in the background (see NativePageAssassin),
+ // recreate the NativePage now.
+ if (getNativePage() instanceof FrozenNativePage) {
+ maybeShowNativePage(getUrl(), true);
+ }
+ NativePageAssassin.getInstance().tabShown(this);
+ }
+
+ @Override
+ protected void restoreIfNeededInternal() {
+ super.restoreIfNeededInternal();
+ }
+
+ @Override
+ protected void hideInternal() {
+ super.hideInternal();
+ cancelEnableFullscreenLoadDelay();
+
+ // Allow this tab's NativePage to be frozen if it stays hidden for a while.
+ NativePageAssassin.getInstance().tabHidden(this);
+
+ mTabRedirectHandler.clear();
+ }
+
+ /**
+ * Checks if spdy proxy is enabled for input url.
+ * @param url Input url to check for spdy setting.
+ * @return true if url is enabled for spdy proxy.
+ */
+ protected boolean isSpdyProxyEnabledForUrl(String url) {
+ if (DataReductionProxySettings.getInstance().isDataReductionProxyEnabled()
+ && url != null && !url.toLowerCase(Locale.US).startsWith("https://")
+ && !isIncognito()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void goBack() {
+ mBackgroundContentViewHelper.recordBack();
+ super.goBack();
+ }
+
+ @Override
+ public void reload() {
+ mBackgroundContentViewHelper.recordReload();
+ super.reload();
+ }
+
+ @Override
+ public void reloadIgnoringCache() {
+ mBackgroundContentViewHelper.recordReload();
+ super.reloadIgnoringCache();
+ }
+
+ @Override
+ public int loadUrl(LoadUrlParams params) {
+ try {
+ TraceEvent.begin("ChromeTab.loadUrl");
+
+ // The data reduction proxy can only be set to pass through mode via loading an image in
+ // a new tab. We squirrel away whether pass through mode was set, and check it in:
+ // @see ChromeWebContentsDelegateAndroid#onLoadStopped()
+ mLastPageLoadHasSpdyProxyPassthroughHeaders = false;
+ if (TextUtils.equals(params.getVerbatimHeaders(), PAGESPEED_PASSTHROUGH_HEADER)) {
+ mLastPageLoadHasSpdyProxyPassthroughHeaders = true;
+ }
+
+ // TODO(tedchoc): When showing the android NTP, delay the call to nativeLoadUrl until
+ // the android view has entirely rendered.
+ if (!mIsNativePageCommitPending) {
+ mIsNativePageCommitPending = maybeShowNativePage(params.getUrl(), false);
+ }
+
+ return super.loadUrl(params);
+ } finally {
+ TraceEvent.end("ChromeTab.loadUrl");
+ }
+ }
+
+ @VisibleForTesting
+ public AuthenticatorNavigationInterceptor getAuthenticatorHelper() {
+ return getInterceptNavigationDelegate().mAuthenticatorHelper;
+ }
+
+ /**
+ * @return the TabRedirectHandler for the tab.
+ */
+ public TabRedirectHandler getTabRedirectHandler() {
+ return mTabRedirectHandler;
+ }
+
+ private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate {
+ final ExternalNavigationHandler mExternalNavHandler;
+ final AuthenticatorNavigationInterceptor mAuthenticatorHelper;
+
+ InterceptNavigationDelegateImpl() {
+ mExternalNavHandler = new ExternalNavigationHandler(mActivity);
+
+ mAuthenticatorHelper = ((ChromeMobileApplication) getApplicationContext())
+ .createAuthenticatorNavigationInterceptor(ChromeTab.this);
+ }
+
+ public boolean shouldIgnoreNewTab(String url, boolean incognito) {
+ if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthenticatorUrl(url)) {
+ return true;
+ }
+
+ ExternalNavigationParams params = new ExternalNavigationParams.Builder(url, incognito)
+ .setTab(ChromeTab.this)
+ .setOpenInNewTab(true)
+ .build();
+ return mExternalNavHandler.shouldOverrideUrlLoading(params)
+ != ExternalNavigationHandler.OverrideUrlLoadingResult.NO_OVERRIDE;
+ }
+
+ @Override
+ public boolean shouldIgnoreNavigation(NavigationParams navigationParams) {
+ final String url = navigationParams.url;
+
+ if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthenticatorUrl(url)) {
+ return true;
+ }
+
+ mTabRedirectHandler.updateNewUrlLoading(navigationParams.pageTransitionType,
+ navigationParams.isRedirect,
+ navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover,
+ mActivity.getLastUserInteractionTime(), getLastCommittedEntryIndex());
+ final boolean shouldCloseTab = shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent();
+ boolean isInitialTabLaunchInBackground =
+ getLaunchType() == TabLaunchType.FROM_LONGPRESS_BACKGROUND && shouldCloseTab;
+ // http://crbug.com/448977: If a new tab is closed by this overriding, we should open an
+ // Intent in a new tab when Chrome receives it again.
+ ExternalNavigationParams params = new ExternalNavigationParams.Builder(
+ url, isIncognito(), getReferrerUrl(), navigationParams.pageTransitionType,
+ navigationParams.isRedirect)
+ .setTab(ChromeTab.this)
+ .setApplicationMustBeInForeground(true)
+ .setRedirectHandler(mTabRedirectHandler)
+ .setOpenInNewTab(shouldCloseTab)
+ .setIsBackgroundTabNavigation(isHidden() && !isInitialTabLaunchInBackground)
+ .setIsMainFrame(navigationParams.isMainFrame)
+ .setNeedsToCloseTabAfterIncognitoDialog(shouldCloseTab
+ && navigationParams.isMainFrame)
+ .build();
+ ExternalNavigationHandler.OverrideUrlLoadingResult result =
+ mExternalNavHandler.shouldOverrideUrlLoading(params);
+ mLastOverrideUrlLoadingResult = result;
+ switch (result) {
+ case OVERRIDE_WITH_EXTERNAL_INTENT:
+ assert mExternalNavHandler.canExternalAppHandleUrl(url);
+ if (navigationParams.isMainFrame) {
+ onOverrideUrlLoadingAndLaunchIntent();
+ }
+ return true;
+ case OVERRIDE_WITH_CLOBBERING_TAB:
+ mShouldClearRedirectHistoryForTabClobbering = true;
+ return true;
+ case OVERRIDE_WITH_INCOGNITO_MODE:
+ if (!shouldCloseTab && navigationParams.isMainFrame) {
+ onOverrideUrlLoadingAndLaunchIntent();
+ }
+ return true;
+ case NO_OVERRIDE:
+ default:
+ if (navigationParams.isExternalProtocol) {
+ logBlockedNavigationToDevToolsConsole(url);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void logBlockedNavigationToDevToolsConsole(String url) {
+ int resId = mExternalNavHandler.canExternalAppHandleUrl(url)
+ ? R.string.blocked_navigation_warning
+ : R.string.unreachable_navigation_warning;
+ getWebContents().addMessageToDevToolsConsole(
+ ConsoleMessageLevel.WARNING, getApplicationContext().getString(resId, url));
+ }
+
+ private String getReferrerUrl() {
+ // At this point, ContentView#getCurrentUrl() has already changed to
+ // be the URL to be loaded, so we need to check the last navigation entry
+ // instead. Note that for browser-initiated navigations, we rely on the
+ // mLastLoadUrlAddress check bailing early.
+ if (getWebContents() != null) {
+ return getWebContents().getNavigationController()
+ .getOriginalUrlForVisibleNavigationEntry();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ protected boolean shouldIgnoreNewTab(String url, boolean incognito) {
+ InterceptNavigationDelegateImpl delegate = getInterceptNavigationDelegate();
+ return delegate != null && delegate.shouldIgnoreNewTab(url, incognito);
+ }
+
+ private int getLastCommittedEntryIndex() {
+ if (getWebContents() == null) return -1;
+ return getWebContents().getNavigationController().getLastCommittedEntryIndex();
+ }
+
+ /**
+ * @return A potential fallback texture id to use when trying to draw this tab.
+ */
+ public int getFallbackTextureId() {
+ return INVALID_TAB_ID;
+ }
+
+ private boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent() {
+ if (getWebContents() == null) return false;
+ if (!getWebContents().getNavigationController().canGoToOffset(0)) return true;
+
+ // http://crbug/415948 : if the last committed entry index which was saved before this
+ // navigation is invalid, it means that this navigation is the first one since this tab was
+ // created.
+ // In such case, we would like to close this tab.
+ if (mTabRedirectHandler.isOnNavigation()) {
+ return mTabRedirectHandler.getLastCommittedEntryIndexBeforeStartingNavigation()
+ == TabRedirectHandler.INVALID_ENTRY_INDEX;
+ }
+ return false;
+ }
+
+ /**
+ * Called when Chrome decides to override URL loading and show an intent picker.
+ */
+ protected void onOverrideUrlLoadingAndLaunchIntent() {
+ if (getWebContents() == null) return;
+
+ // Before leaving Chrome, close the empty child tab.
+ // If a new tab is created through JavaScript open to load this
+ // url, we would like to close it as we will load this url in a
+ // different Activity.
+ if (shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent()) {
+ getChromeWebContentsDelegateAndroid().closeContents();
+ } else if (mTabRedirectHandler.isOnNavigation()) {
+ int lastCommittedEntryIndexBeforeNavigation =
+ mTabRedirectHandler.getLastCommittedEntryIndexBeforeStartingNavigation();
+ if (getLastCommittedEntryIndex() > lastCommittedEntryIndexBeforeNavigation) {
+ // http://crbug/426679 : we want to go back to the last committed entry index which
+ // was saved before this navigation, and remove the empty entries from the
+ // navigation history.
+ mClearAllForwardHistoryRequired = true;
+ getWebContents().getNavigationController().goToNavigationIndex(
+ lastCommittedEntryIndexBeforeNavigation);
+ }
+ }
+ }
+
+ @Override
+ protected InterceptNavigationDelegateImpl getInterceptNavigationDelegate() {
+ return (InterceptNavigationDelegateImpl) super.getInterceptNavigationDelegate();
+ }
+
+ @Override
+ public void setClosing(boolean closing) {
+ if (closing) mBackgroundContentViewHelper.recordTabClose();
+ super.setClosing(closing);
+ }
+
+ /**
+ * @return The reader mode manager for this tab that handles UI events for reader mode.
+ */
+ public ReaderModeManager getReaderModeManager() {
+ return mReaderModeManager;
+ }
+
+ public ReaderModeActivityDelegate getReaderModeActivityDelegate() {
+ if (!(mActivity instanceof CompositorChromeActivity)) return null;
+ return ((CompositorChromeActivity) mActivity).getReaderModeActivityDelegate();
+ }
+
+ /**
+ * Shows a native page for url if it's a valid chrome-native URL. Otherwise, does nothing.
+ * @param url The url of the current navigation.
+ * @param isReload Whether the current navigation is a reload.
+ * @return True, if a native page was displayed for url.
+ */
+ private boolean maybeShowNativePage(String url, boolean isReload) {
+ NativePage candidateForReuse = isReload ? null : getNativePage();
+ NativePage nativePage = NativePageFactory.createNativePageForURL(url, candidateForReuse,
+ this, mActivity.getTabModelSelector(), mActivity);
+ if (nativePage != null) {
+ showNativePage(nativePage);
+ notifyPageTitleChanged();
+ notifyFaviconChanged();
+ return true;
+ }
+ return false;
+ }
+
+ // TODO(dtrainor): Port more methods to the observer.
+ private final TabObserver mTabObserver = new EmptyTabObserver() {
+ @Override
+ public void onContentChanged(Tab tab) {
+ mLoadProgressAtViewSwapInTime = 0;
+ }
+
+ @Override
+ public void onSSLStateUpdated(Tab tab) {
+ PolicyAuditor auditor =
+ ((ChromeMobileApplication) getApplicationContext()).getPolicyAuditor();
+ auditor.notifyCertificateFailure(getWebContents(), getApplicationContext());
+ updateFullscreenEnabledState();
+ }
+
+ @Override
+ public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) {
+ if (!didStartLoad) return;
+
+ String url = tab.getUrl();
+ // Simulate the PAGE_LOAD_STARTED notification that we did not get.
+ didStartPageLoad(url, false);
+ if (didFinishLoad) {
+ // Simulate the PAGE_LOAD_FINISHED notification that we did not get.
+ didFinishPageLoad();
+ }
+ }
+ };
+
+ @VisibleForTesting
+ public OverrideUrlLoadingResult getLastOverrideUrlLoadingResultForTests() {
+ return mLastOverrideUrlLoadingResult;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698