Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
| index b13f30c6dcd1d7dac97ef5d0b7b303df58d4a7aa..a24f9faa25913fb8e8c8cfede7c69e244020bff0 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java |
| @@ -6,19 +6,36 @@ package org.chromium.chrome.browser.compositor.bottombar.contextualsearch; |
| import android.content.Context; |
| import android.os.Handler; |
| +import android.view.View.MeasureSpec; |
| +import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| +import org.chromium.chrome.R; |
| +import org.chromium.chrome.browser.ChromeActivity; |
| +import org.chromium.chrome.browser.WebContentsFactory; |
| import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; |
| +import org.chromium.chrome.browser.contextualsearch.ContextualSearchContentController; |
| +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.tab.TabRedirectHandler; |
| import org.chromium.components.navigation_interception.InterceptNavigationDelegate; |
| +import org.chromium.components.navigation_interception.NavigationParams; |
| import org.chromium.components.web_contents_delegate_android.WebContentsDelegateAndroid; |
| +import org.chromium.content.browser.ContentView; |
| +import org.chromium.content.browser.ContentViewClient; |
| import org.chromium.content.browser.ContentViewCore; |
| +import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.WebContents; |
| +import org.chromium.content_public.browser.WebContentsObserver; |
| +import org.chromium.content_public.common.ConsoleMessageLevel; |
| +import org.chromium.ui.base.WindowAndroid; |
| /** |
| * Controls the Contextual Search Panel. |
| */ |
| public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| - implements ContextualSearchPanelDelegate { |
| + implements ContextualSearchPanelDelegate, ContextualSearchContentController { |
| /** |
| * State of the Contextual Search Panel. |
| @@ -55,6 +72,14 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| CLOSE_BUTTON; |
| } |
| + // The animation duration of a URL being promoted to a tab when triggered by an |
| + // intercept navigation. This is faster than the standard tab promotion animation |
| + // so that it completes before the navigation. |
| + private static final long INTERCEPT_NAVIGATION_PROMOTION_ANIMATION_DURATION_MS = 40; |
| + // We blacklist this URL because malformed URLs may bring up this page. |
| + private static final String BLACKLISTED_URL = "about:blank"; |
| + private static final String INTENT_URL_PREFIX = "intent:"; |
| + |
| /** |
| * The ContentViewCore that this panel will display. |
| */ |
| @@ -65,6 +90,23 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| */ |
| private long mNativeContextualSearchPanelPtr; |
| + private final WebContentsDelegateAndroid mWebContentsDelegate; |
| + |
| + private ChromeActivity mActivity; |
| + private WindowAndroid mWindowAndroid; |
| + |
| + private WebContentsObserver mWebContentsObserver; |
| + private TabRedirectHandler mTabRedirectHandler; |
| + private boolean mDidLoadAnyUrl; |
| + private boolean mDidPromoteSearchNavigation; |
| + private boolean mIsContentViewShowing; |
| + |
| + private ContextualSearchContentController mContentController; |
|
David Trainor- moved to gerrit
2015/08/28 22:02:18
This parameter feels weird because we're just sett
mdjones
2015/08/28 23:51:58
Yeah, the manager does something similar. I'm not
|
| + |
| + // http://crbug.com/522266 : An instance of InterceptNavigationDelegateImpl should be kept in |
| + // java layer. Otherwise, the instance could be garbage-collected unexpectedly. |
| + private InterceptNavigationDelegateImpl mInterceptNavigationDelegate; |
| + |
| /** |
| * The delay after which the hide progress will be hidden. |
| */ |
| @@ -106,6 +148,38 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| public ContextualSearchPanel(Context context, LayoutUpdateHost updateHost) { |
| super(context, updateHost); |
| nativeInit(); |
| + mContentController = this; |
| + |
| + mWebContentsDelegate = new WebContentsDelegateAndroid() { |
|
David Trainor- moved to gerrit
2015/08/28 22:02:18
I'm assuming all of the actual logic is moved from
mdjones
2015/08/28 23:51:58
Correct
|
| + @Override |
| + public void onLoadStarted() { |
| + super.onLoadStarted(); |
| + setProgressBarCompletion(0); |
| + setProgressBarVisible(true); |
| + requestUpdate(); |
| + } |
| + |
| + @Override |
| + public void onLoadStopped() { |
| + super.onLoadStopped(); |
| + // Hides the Progress Bar after a delay to make sure it is rendered for at least |
| + // a few frames, otherwise its completion won't be visually noticeable. |
| + new Handler().postDelayed(new Runnable() { |
| + @Override |
| + public void run() { |
| + setProgressBarVisible(false); |
| + requestUpdate(); |
| + } |
| + }, HIDE_PROGRESS_BAR_DELAY); |
| + } |
| + |
| + @Override |
| + public void onLoadProgressChanged(int progress) { |
| + super.onLoadProgressChanged(progress); |
| + setProgressBarCompletion(progress); |
| + requestUpdate(); |
| + } |
| + }; |
| } |
| // ============================================================================================ |
| @@ -124,16 +198,6 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| // Contextual Search Manager Integration |
| // ============================================================================================ |
| - /** |
| - * Sets the visibility of the Search Content View. |
| - * @param isVisible True to make it visible. |
| - */ |
| - public void setSearchContentViewVisibility(boolean isVisible) { |
| - if (getManagementDelegate() != null) { |
| - getManagementDelegate().setSearchContentViewVisibility(isVisible); |
| - } |
| - } |
| - |
| @Override |
| public void setPreferenceState(boolean enabled) { |
| if (getManagementDelegate() != null) { |
| @@ -155,6 +219,7 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| @Override |
| protected void onClose(StateChangeReason reason) { |
| + destroyContentView(); |
| getManagementDelegate().onCloseContextualSearch(reason); |
| } |
| @@ -183,7 +248,7 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| if (ty > 0 && getPanelState() == PanelState.MAXIMIZED) { |
| // Resets the Search Content View scroll position when swiping the Panel down |
| // after being maximized. |
| - getManagementDelegate().resetSearchContentViewScroll(); |
| + resetSearchContentViewScroll(); |
| } |
| // Negative ty value means an upward movement so subtracting ty means expanding the panel. |
| @@ -252,6 +317,39 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| } |
| } |
| + /** |
| + * Called to check if an external navigation is being done and take the appropriate action: |
| + * Auto-promotes the panel into a separate tab if that's not already being done. |
| + * @param url The URL we are navigating to. |
| + */ |
| + private void onExternalNavigation(String url) { |
| + if (isFullscreenSizePanel()) { |
| + // Consider the ContentView height to be fullscreen, and inform the system that |
| + // the Toolbar is always visible (from the Compositor's perspective), even though |
| + // the Toolbar and Base Page might be offset outside the screen. This means the |
| + // renderer will consider the ContentView height to be the fullscreen height |
| + // minus the Toolbar height. |
| + // |
| + // This is necessary to fix the bugs: crbug.com/510205 and crbug.com/510206 |
| + mContentViewCore.getWebContents().updateTopControlsState(false, true, false); |
| + } else { |
| + mContentViewCore.getWebContents().updateTopControlsState(true, false, false); |
| + } |
| + |
| + if (!mDidPromoteSearchNavigation |
| + && !BLACKLISTED_URL.equals(url) |
| + && !url.startsWith(INTENT_URL_PREFIX) |
| + && shouldPromoteSearchNavigation()) { |
| + // Do not promote to a regular tab if we're loading our Resolved Search |
| + // URL, otherwise we'll promote it when prefetching the Serp. |
| + // Don't promote URLs when they are navigating to an intent - this is |
| + // handled by the InterceptNavigationDelegate which uses a faster |
| + // maximizing animation. |
| + mDidPromoteSearchNavigation = true; |
| + maximizePanelThenPromoteToTab(StateChangeReason.SERP_NAVIGATION); |
| + } |
| + } |
| + |
| // ============================================================================================ |
| // Gesture Event helpers |
| // ============================================================================================ |
| @@ -368,6 +466,230 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| } |
| // ============================================================================================ |
| + // ContextualSearchContentController |
| + // ============================================================================================ |
| + |
| + @Override |
| + public void createNewContentView() { |
| + if (mContentViewCore != null) { |
| + destroyContentView(); |
| + } |
| + |
| + mTabRedirectHandler = new TabRedirectHandler(mActivity); |
| + mContentViewCore = new ContentViewCore(mActivity); |
| + |
| + // Adds a ContentViewClient to override the default fullscreen size. |
| + if (!isFullscreenSizePanel()) { |
| + mContentViewCore.setContentViewClient(new ContentViewClient() { |
| + @Override |
| + public int getDesiredWidthMeasureSpec() { |
| + return MeasureSpec.makeMeasureSpec( |
| + getSearchContentViewWidthPx(), |
| + MeasureSpec.EXACTLY); |
| + } |
| + |
| + @Override |
| + public int getDesiredHeightMeasureSpec() { |
| + return MeasureSpec.makeMeasureSpec( |
| + getSearchContentViewHeightPx(), |
| + MeasureSpec.EXACTLY); |
| + } |
| + }); |
| + } |
| + |
| + ContentView cv = new ContentView(mActivity, mContentViewCore); |
| + // Creates an initially hidden WebContents which gets shown when the panel is opened. |
| + mContentViewCore.initialize(cv, cv, |
| + WebContentsFactory.createWebContents(false, true), mWindowAndroid); |
| + |
| + // Transfers the ownership of the WebContents to the native ContextualSearchManager. |
| + nativeSetWebContents(mNativeContextualSearchPanelPtr, mContentViewCore, |
| + mWebContentsDelegate); |
| + |
| + mWebContentsObserver = |
| + new WebContentsObserver(mContentViewCore.getWebContents()) { |
| + @Override |
| + public void didStartLoading(String url) { |
| + mDidPromoteSearchNavigation = false; |
| + } |
| + |
| + @Override |
| + public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId, |
| + boolean isMainFrame, String validatedUrl, boolean isErrorPage, |
| + boolean isIframeSrcdoc) { |
| + if (isMainFrame) onExternalNavigation(validatedUrl); |
| + } |
| + |
| + @Override |
| + public void didNavigateMainFrame(String url, String baseUrl, |
| + boolean isNavigationToDifferentPage, boolean isNavigationInPage, |
| + int httpResultCode) { |
| + handleDidNavigateMainFrame(url, httpResultCode); |
| + } |
| + |
| + @Override |
| + public void didFinishLoad(long frameId, String validatedUrl, |
| + boolean isMainFrame) { |
| + getManagementDelegate().onSearchResultsLoaded(); |
| + } |
| + }; |
| + |
| + mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(); |
| + nativeSetInterceptNavigationDelegate(mNativeContextualSearchPanelPtr, |
| + mInterceptNavigationDelegate, mContentViewCore.getWebContents()); |
| + getManagementDelegate().onContentViewCreated(mContentViewCore); |
| + } |
| + |
| + @Override |
| + public void destroyContentView() { |
| + if (mContentViewCore != null) { |
| + nativeDestroyWebContents(mNativeContextualSearchPanelPtr); |
| + mContentViewCore.getWebContents().destroy(); |
| + mContentViewCore.destroy(); |
| + mContentViewCore = null; |
| + if (mWebContentsObserver != null) { |
| + mWebContentsObserver.destroy(); |
| + mWebContentsObserver = null; |
| + } |
| + } |
| + |
| + // This should be called last here. The setSearchContentViewVisibility method |
| + // will change the visibility the SearchContentView but also set the value of the |
| + // internal property mIsSearchContentViewShowing. If we call this after deleting |
| + // the SearchContentView, it will be faster, because only the internal property |
| + // will be changed, since there will be no need to change the visibility of the |
| + // SearchContentView. |
| + setSearchContentViewVisibility(false); |
| + |
| + getManagementDelegate().onContentViewDestroyed(); |
| + } |
| + |
| + @Override |
| + public void handleDidNavigateMainFrame(String url, int httpResultCode) { |
| + if (shouldPromoteSearchNavigation()) { |
| + onExternalNavigation(url); |
| + } else { |
| + // Could be just prefetching, check if that failed. |
| + boolean isFailure = isHttpFailureCode(httpResultCode); |
| + getManagementDelegate().onContextualSearchRequestNavigation(isFailure); |
| + } |
| + mDidLoadAnyUrl = false; |
| + } |
| + |
| + @Override |
| + public void loadUrl(String url) { |
| + mContentController.destroyContentView(); |
| + createNewPanelContentView(); |
| + if (mContentViewCore != null && mContentViewCore.getWebContents() != null) { |
| + mDidLoadAnyUrl = true; |
| + mContentViewCore.getWebContents().getNavigationController().loadUrl( |
| + new LoadUrlParams(url)); |
| + } |
| + } |
| + |
| + // ============================================================================================ |
| + // Utilities |
| + // ============================================================================================ |
| + |
| + public void resetSearchContentViewScroll() { |
| + if (mContentViewCore != null) { |
| + mContentViewCore.scrollTo(0, 0); |
| + } |
| + } |
| + |
| + public float getSearchContentViewVerticalScroll() { |
| + return mContentViewCore != null |
| + ? mContentViewCore.computeVerticalScrollOffset() : -1.f; |
| + } |
| + |
| + /** |
| + * Sets the visibility of the Search Content View. |
| + * @param isVisible True to make it visible. |
| + */ |
| + public void setSearchContentViewVisibility(boolean isVisible) { |
| + if (mIsContentViewShowing == isVisible) return; |
| + |
| + mIsContentViewShowing = isVisible; |
| + getManagementDelegate().onContentViewVisibilityChanged(isVisible); |
| + |
| + if (isVisible) { |
| + // The CVC is created with the search request, but if none was made we'll need |
| + // one in order to display an empty panel. |
| + if (mContentViewCore == null) { |
| + createNewPanelContentView(); |
| + } |
| + if (mContentViewCore != null) mContentViewCore.onShow(); |
| + setWasSearchContentViewSeen(); |
| + } else { |
| + if (mContentViewCore != null) mContentViewCore.onHide(); |
| + } |
| + } |
| + |
| + /** |
| + * @return Whether the given HTTP result code represents a failure or not. |
| + */ |
| + private boolean isHttpFailureCode(int httpResultCode) { |
| + return httpResultCode <= 0 || httpResultCode >= 400; |
| + } |
| + |
| + /** |
| + * @return whether a navigation in the search content view should promote to a separate tab. |
| + */ |
| + private boolean shouldPromoteSearchNavigation() { |
| + // A navigation can be due to us loading a URL, or a touch in the search content view. |
| + // Require a touch, but no recent loading, in order to promote to a separate tab. |
| + // Note that tapping the opt-in button requires checking for recent loading. |
| + return didTouchSearchContentView() && !mDidLoadAnyUrl; |
| + } |
| + |
| + // ============================================================================================ |
| + // InterceptNavigationDelegateImpl |
| + // ============================================================================================ |
| + |
| + // Used to intercept intent navigations. |
| + // TODO(jeremycho): Consider creating a Tab with the Panel's ContentViewCore, |
| + // which would also handle functionality like long-press-to-paste. |
| + private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate { |
| + final ExternalNavigationHandler mExternalNavHandler = new ExternalNavigationHandler( |
| + mActivity); |
| + @Override |
| + public boolean shouldIgnoreNavigation(NavigationParams navigationParams) { |
| + mTabRedirectHandler.updateNewUrlLoading(navigationParams.pageTransitionType, |
| + navigationParams.isRedirect, |
| + navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover, |
| + mActivity.getLastUserInteractionTime(), TabRedirectHandler.INVALID_ENTRY_INDEX); |
| + |
| + ExternalNavigationParams params = new ExternalNavigationParams.Builder( |
| + navigationParams.url, false, navigationParams.referrer, |
| + navigationParams.pageTransitionType, navigationParams.isRedirect) |
| + .setApplicationMustBeInForeground(true) |
| + .setRedirectHandler(mTabRedirectHandler) |
| + .setIsMainFrame(navigationParams.isMainFrame) |
| + .build(); |
| + if (mExternalNavHandler.shouldOverrideUrlLoading(params) |
| + != OverrideUrlLoadingResult.NO_OVERRIDE) { |
| + maximizePanelThenPromoteToTab(StateChangeReason.TAB_PROMOTION, |
| + INTERCEPT_NAVIGATION_PROMOTION_ANIMATION_DURATION_MS); |
| + return true; |
| + } |
| + if (navigationParams.isExternalProtocol) { |
| + ContentViewCore baseContentView = getManagementDelegate().getBaseContentView(); |
| + if (baseContentView != null) { |
| + int resId = mExternalNavHandler.canExternalAppHandleUrl(navigationParams.url) |
| + ? R.string.blocked_navigation_warning |
| + : R.string.unreachable_navigation_warning; |
| + String message = mActivity.getApplicationContext().getString( |
| + resId, navigationParams.url); |
| + baseContentView.getWebContents().addMessageToDevToolsConsole( |
| + ConsoleMessageLevel.WARNING, message); |
| + } |
| + return true; |
| + } |
| + return false; |
| + } |
| + } |
| + |
| + // ============================================================================================ |
| // Panel Delegate |
| // ============================================================================================ |
| @@ -454,32 +776,6 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| } |
| @Override |
| - public void onLoadStarted() { |
| - setProgressBarCompletion(0); |
| - setProgressBarVisible(true); |
| - requestUpdate(); |
| - } |
| - |
| - @Override |
| - public void onLoadStopped() { |
| - // Hides the Progress Bar after a delay to make sure it is rendered for at least |
| - // a few frames, otherwise its completion won't be visually noticeable. |
| - new Handler().postDelayed(new Runnable() { |
| - @Override |
| - public void run() { |
| - setProgressBarVisible(false); |
| - requestUpdate(); |
| - } |
| - }, HIDE_PROGRESS_BAR_DELAY); |
| - } |
| - |
| - @Override |
| - public void onLoadProgressChanged(int progress) { |
| - setProgressBarCompletion(progress); |
| - requestUpdate(); |
| - } |
| - |
| - @Override |
| public PanelState getPanelState() { |
| // NOTE(pedrosimonetti): exposing superclass method to the interface. |
| return super.getPanelState(); |
| @@ -525,6 +821,38 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| return mSearchPanelFeatures.shouldAnimatePanelCloseOnPromoteToTab(); |
| } |
| + @Override |
| + public boolean isContentViewShowing() { |
| + return mIsContentViewShowing; |
| + } |
| + |
| + @Override |
| + public void setChromeActivity(ChromeActivity activity) { |
| + mActivity = activity; |
| + mWindowAndroid = mActivity.getWindowAndroid(); |
| + } |
| + |
| + @Override |
| + public void createNewPanelContentView() { |
| + mContentController.createNewContentView(); |
| + } |
| + |
| + @Override |
| + public void loadUrlInPanel(String url) { |
| + mContentController.loadUrl(url); |
| + } |
| + |
| + @Override |
| + public void setContentController(ContextualSearchContentController controller) { |
| + mContentController = controller; |
| + } |
| + |
| + @VisibleForTesting |
| + @Override |
| + public ContextualSearchContentController getContentController() { |
| + return this; |
| + } |
| + |
| // ============================================================================================ |
| // Methods for managing this panel's ContentViewCore. |
| // ============================================================================================ |
| @@ -546,42 +874,14 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| return mContentViewCore; |
| } |
| - public void resetContentViewCore() { |
| - // TODO(mdjones): Merge all code related to deleting/resetting the ContentViewCore. |
| - mContentViewCore = null; |
| - } |
| - |
| - @Override |
| - public void destroy() { |
| - nativeDestroy(mNativeContextualSearchPanelPtr); |
| - } |
| - |
| @Override |
| public void removeLastHistoryEntry(String historyUrl, long urlTimeMs) { |
| nativeRemoveLastHistoryEntry(mNativeContextualSearchPanelPtr, historyUrl, urlTimeMs); |
| } |
| - @Override |
| - public void setWebContents(ContentViewCore contentView, WebContentsDelegateAndroid delegate) { |
| - mContentViewCore = contentView; |
| - nativeSetWebContents(mNativeContextualSearchPanelPtr, contentView, delegate); |
| - } |
| - |
| - @Override |
| - public void destroyWebContents() { |
| - nativeDestroyWebContents(mNativeContextualSearchPanelPtr); |
| - } |
| - |
| - @Override |
| - public void releaseWebContents() { |
| - nativeReleaseWebContents(mNativeContextualSearchPanelPtr); |
| - } |
| - |
| - @Override |
| - public void setInterceptNavigationDelegate( |
| - InterceptNavigationDelegate delegate, WebContents webContents) { |
| - nativeSetInterceptNavigationDelegate(mNativeContextualSearchPanelPtr, |
| - delegate, webContents); |
| + @VisibleForTesting |
| + public void destroy() { |
| + nativeDestroy(mNativeContextualSearchPanelPtr); |
| } |
| // Native calls. |
| @@ -592,7 +892,6 @@ public class ContextualSearchPanel extends ContextualSearchPanelAnimation |
| private native void nativeSetWebContents(long nativeContextualSearchPanel, |
| ContentViewCore contentViewCore, WebContentsDelegateAndroid delegate); |
| private native void nativeDestroyWebContents(long nativeContextualSearchPanel); |
| - private native void nativeReleaseWebContents(long nativeContextualSearchPanel); |
| private native void nativeSetInterceptNavigationDelegate(long nativeContextualSearchPanel, |
| InterceptNavigationDelegate delegate, WebContents webContents); |
| } |