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; |
+ } |
+} |