[TabModal] Add toolbar animation on JS dialog shown
Add animation for the Android toolbar view to slide in when JavaScript
tab modal dialog is showing and the renderer is blocked on input events.
Bug: 817461
Change-Id: I3ed69ffabe736100ac209be59f34eeedd4189cee
Reviewed-on: https://chromium-review.googlesource.com/942378
Commit-Queue: Becky Zhou <[email protected]>
Reviewed-by: Ted Choc <[email protected]>
Reviewed-by: Matthew Jones <[email protected]>
Cr-Commit-Position: refs/heads/master@{#547062}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
index ff9392d..49d5c9ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -544,6 +544,9 @@
private boolean shouldShowAndroidControls() {
if (mBrowserControlsAndroidViewHidden) return false;
+ if (getTab() != null && getTab().getControlsOffsetHelper().isControlsOffsetOverridden()) {
+ return true;
+ }
boolean showControls = !drawControlsAsTexture();
ContentViewCore contentViewCore = getActiveContentViewCore();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
index 81a0f1d..3d1f511 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java
@@ -206,8 +206,8 @@
* @param dialog The dialog to be cancelled.
*/
public void cancelDialog(ModalDialogView dialog) {
- dismissDialog(dialog);
dialog.getController().onCancel();
+ dismissDialog(dialog);
}
/**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
index 6c68d7d..72fd1eed 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java
@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.modaldialog;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.res.Resources;
import android.view.Gravity;
import android.view.View;
@@ -18,6 +20,7 @@
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBrowserControlsOffsetHelper;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
@@ -25,11 +28,16 @@
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.UiUtils;
+import org.chromium.ui.interpolators.BakedBezierInterpolator;
/**
* The presenter that displays a single tab modal dialog.
*/
-public class TabModalPresenter extends ModalDialogManager.Presenter {
+public class TabModalPresenter
+ extends ModalDialogManager.Presenter implements TabBrowserControlsOffsetHelper.Observer {
+ // TODO(huayinz): Confirm duration with UX.
+ private static final int ENTER_EXIT_ANIMATION_DURATION_MS = 200;
+
/** The activity displaying the dialogs. */
private final ChromeActivity mChromeActivity;
@@ -54,6 +62,12 @@
/** Whether the dialog container is brought to the front in its parent. */
private boolean mContainerIsAtFront;
+ /**
+ * Whether an enter animation on the dialog container should run when
+ * {@link #onBrowserControlsFullyVisible} is called.
+ */
+ private boolean mRunEnterAnimationOnCallback;
+
/** Whether the action bar on selected text is temporarily cleared for showing dialogs. */
private boolean mDidClearTextControls;
@@ -64,6 +78,9 @@
*/
private View mDefaultNextSiblingView;
+ /** Enter and exit animation duration that can be overwritten in tests. */
+ private int mEnterExitAnimationDurationMs;
+
/**
* Constructor for initializing dialog container.
* @param chromeActivity The activity displaying the dialogs.
@@ -71,6 +88,7 @@
public TabModalPresenter(ChromeActivity chromeActivity) {
mChromeActivity = chromeActivity;
mHasBottomControls = mChromeActivity.getBottomSheet() != null;
+ mEnterExitAnimationDurationMs = ENTER_EXIT_ANIMATION_DURATION_MS;
if (mHasBottomControls) {
mBottomSheetObserver = new EmptyBottomSheetObserver() {
@@ -91,11 +109,12 @@
protected void addDialogView(View dialogView) {
if (mDialogContainer == null) initDialogContainer();
setBrowserControlsAccess(true);
- mDialogContainer.setVisibility(View.VISIBLE);
-
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
- MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
- mDialogContainer.addView(dialogView, params);
+ // Don't show the dialog container before browser controls are guaranteed fully visible.
+ if (mActiveTab.getControlsOffsetHelper().areBrowserControlsFullyVisible()) {
+ runEnterAnimation(dialogView);
+ } else {
+ mRunEnterAnimationOnCallback = true;
+ }
mChromeActivity.addViewObscuringAllTabs(mDialogContainer);
updateContainerHierarchy(true);
}
@@ -103,13 +122,27 @@
@Override
protected void removeDialogView(View dialogView) {
setBrowserControlsAccess(false);
- // Clear focus so that keyboard can hide accordingly while entering tab switcher.
- dialogView.clearFocus();
- mDialogContainer.removeView(dialogView);
- mDialogContainer.setVisibility(View.GONE);
+ // Don't run exit animation if enter animation has not yet started.
+ if (mRunEnterAnimationOnCallback) {
+ mRunEnterAnimationOnCallback = false;
+ } else {
+ // Clear focus so that keyboard can hide accordingly while entering tab switcher.
+ dialogView.clearFocus();
+ runExitAnimation(dialogView);
+ }
mChromeActivity.removeViewObscuringAllTabs(mDialogContainer);
}
+ @Override
+ public void onBrowserControlsFullyVisible(Tab tab) {
+ if (getModalDialog() == null) return;
+ assert mActiveTab == tab;
+ if (mRunEnterAnimationOnCallback) {
+ mRunEnterAnimationOnCallback = false;
+ runEnterAnimation(getModalDialog().getView());
+ }
+ }
+
/**
* Change view hierarchy for the dialog container to be either the front most or beneath the
* toolbar.
@@ -146,6 +179,7 @@
dialogContainerStub.setLayoutResource(R.layout.modal_dialog_container);
mDialogContainer = (ViewGroup) dialogContainerStub.inflate();
+ mDialogContainer.setVisibility(View.GONE);
mContainerParent = (ViewGroup) mDialogContainer.getParent();
// The default sibling view is the next view of the dialog container stub in main.xml and
// should not be removed from its parent.
@@ -192,6 +226,7 @@
assert mActiveTab
!= null : "Tab modal dialogs should be shown on top of an active tab.";
+ mActiveTab.getControlsOffsetHelper().addObserver(this);
// Hide contextual search panel so that bottom toolbar will not be
// obscured and back press is not overridden.
ContextualSearchManager contextualSearchManager =
@@ -213,8 +248,6 @@
}
// Force toolbar to show and disable overflow menu.
- // TODO(huayinz): figure out a way to avoid |UpdateBrowserControlsState| being blocked
- // by render process stalled due to javascript dialog.
mActiveTab.onTabModalDialogStateChanged(true);
if (mHasBottomControls) {
@@ -225,6 +258,7 @@
}
menuButton.setEnabled(false);
} else {
+ mActiveTab.getControlsOffsetHelper().removeObserver(this);
// Show the action bar back if it was dismissed when the dialogs were showing.
if (mDidClearTextControls) {
mDidClearTextControls = false;
@@ -242,6 +276,45 @@
}
}
+ /**
+ * Helper method to run fade-in animation when the specified dialog view is shown.
+ * @param dialogView The dialog view to be shown.
+ */
+ private void runEnterAnimation(View dialogView) {
+ mDialogContainer.animate().cancel();
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
+ mDialogContainer.addView(dialogView, params);
+ mDialogContainer.setAlpha(0f);
+ mDialogContainer.setVisibility(View.VISIBLE);
+ mDialogContainer.animate()
+ .setDuration(mEnterExitAnimationDurationMs)
+ .alpha(1f)
+ .setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE)
+ .setListener(null)
+ .start();
+ }
+
+ /**
+ * Helper method to run fade-out animation when the specified dialog view is dismissed.
+ * @param dialogView The dismissed dialog view.
+ */
+ private void runExitAnimation(View dialogView) {
+ mDialogContainer.animate().cancel();
+ mDialogContainer.animate()
+ .setDuration(mEnterExitAnimationDurationMs)
+ .alpha(0f)
+ .setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDialogContainer.setVisibility(View.GONE);
+ mDialogContainer.removeView(dialogView);
+ }
+ })
+ .start();
+ }
+
@VisibleForTesting
View getDialogContainerForTest() {
return mDialogContainer;
@@ -251,4 +324,9 @@
ViewGroup getContainerParentForTest() {
return mContainerParent;
}
+
+ @VisibleForTesting
+ void disableAnimationForTest() {
+ mEnterExitAnimationDurationMs = 0;
+ }
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index 818b3a5..683d8fe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -87,7 +87,6 @@
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.TabModelImpl;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabReparentingParams;
import org.chromium.chrome.browser.util.ColorUtils;
@@ -364,9 +363,7 @@
private TabRedirectHandler mTabRedirectHandler;
private FullscreenManager mFullscreenManager;
- private float mPreviousTopControlsOffsetY = Float.NaN;
- private float mPreviousBottomControlsOffsetY = Float.NaN;
- private float mPreviousContentOffsetY = Float.NaN;
+ private final TabBrowserControlsOffsetHelper mControlsOffsetHelper;
/**
* Indicates whether this tab is detached from any activity and its corresponding
@@ -617,6 +614,8 @@
ContextualSearchTabHelper.createForTab(this);
MediaSessionTabHelper.createForTab(this);
+ mControlsOffsetHelper = new TabBrowserControlsOffsetHelper(this);
+
if (creationState != null) {
mTabUma = new TabUma(creationState);
if (frozenState == null) {
@@ -2017,10 +2016,7 @@
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
notifyContentChanged();
}
- FullscreenManager fullscreenManager = getFullscreenManager();
- if (fullscreenManager != null) {
- fullscreenManager.setPositionsForTabToNonFullscreen();
- }
+ mControlsOffsetHelper.showAndroidControls(false);
}
/**
@@ -2106,9 +2102,7 @@
mInfoBarContainer = null;
}
- mPreviousTopControlsOffsetY = Float.NaN;
- mPreviousBottomControlsOffsetY = Float.NaN;
- mPreviousContentOffsetY = Float.NaN;
+ mControlsOffsetHelper.clearPreviousPositions();
mNeedsReload = false;
}
@@ -2806,33 +2800,6 @@
}
/**
- * Called when offset values related with fullscreen functionality has been changed by the
- * compositor.
- * @param topControlsOffsetY The Y offset of the top controls in physical pixels.
- * {@code Float.NaN} if the value is invalid and the cached value should be used.
- * @param bottomControlsOffsetY The Y offset of the bottom controls in physical pixels.
- * {@code Float.NaN} if the value is invalid and the cached value should be used.
- * @param contentOffsetY The Y offset of the content in physical pixels.
- */
- void onOffsetsChanged(
- float topControlsOffsetY, float bottomControlsOffsetY, float contentOffsetY) {
- if (!Float.isNaN(topControlsOffsetY)) mPreviousTopControlsOffsetY = topControlsOffsetY;
- if (!Float.isNaN(bottomControlsOffsetY)) {
- mPreviousBottomControlsOffsetY = bottomControlsOffsetY;
- }
- if (!Float.isNaN(contentOffsetY)) mPreviousContentOffsetY = contentOffsetY;
-
- if (mFullscreenManager == null) return;
- if (isShowingSadTab() || isNativePage()) {
- mFullscreenManager.setPositionsForTabToNonFullscreen();
- } else {
- mFullscreenManager.setPositionsForTab(mPreviousTopControlsOffsetY,
- mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
- }
- TabModelImpl.setActualTabSwitchLatencyMetricRequired();
- }
-
- /**
* Push state about whether or not the browser controls can show or hide to the renderer.
*/
public void updateFullscreenEnabledState() {
@@ -2963,24 +2930,7 @@
*/
public void setFullscreenManager(FullscreenManager manager) {
mFullscreenManager = manager;
- if (mFullscreenManager != null) {
- boolean topOffsetsInitialized = !Float.isNaN(mPreviousTopControlsOffsetY)
- && !Float.isNaN(mPreviousContentOffsetY);
- boolean bottomOffsetsInitialized =
- !Float.isNaN(mPreviousBottomControlsOffsetY);
- boolean isChromeHomeEnabled = FeatureUtilities.isChromeHomeEnabled();
-
- // Make sure the dominant control offsets have been set.
- if ((!topOffsetsInitialized && !isChromeHomeEnabled)
- || (!bottomOffsetsInitialized && isChromeHomeEnabled)) {
- mFullscreenManager.setPositionsForTabToNonFullscreen();
- } else {
- mFullscreenManager.setPositionsForTab(mPreviousTopControlsOffsetY,
- mPreviousBottomControlsOffsetY,
- mPreviousContentOffsetY);
- }
- updateFullscreenEnabledState();
- }
+ mControlsOffsetHelper.resetPositions();
}
/**
@@ -3338,7 +3288,7 @@
mIsRendererUnresponsive = true;
if (mFullscreenManager == null) return;
- mFullscreenManager.setPositionsForTabToNonFullscreen();
+ mControlsOffsetHelper.showAndroidControls(false);
updateBrowserControlsState(BrowserControlsState.SHOWN, false);
}
@@ -3438,9 +3388,27 @@
*/
public void onTabModalDialogStateChanged(boolean isShowing) {
mIsShowingTabModalDialog = isShowing;
- if (mFullscreenManager == null) return;
- mFullscreenManager.setPositionsForTabToNonFullscreen();
- updateBrowserControlsState(BrowserControlsState.SHOWN, false);
+ // Also need to update browser control state after dismissal to refresh the constraints.
+ if (isShowing && areRendererInputEventsIgnored()) {
+ mControlsOffsetHelper.showAndroidControls(true);
+ } else {
+ updateBrowserControlsState(BrowserControlsState.SHOWN,
+ !mControlsOffsetHelper.isControlsOffsetOverridden());
+ }
+ }
+
+ /**
+ * @return Whether input events from the renderer are ignored on the browser side.
+ */
+ boolean areRendererInputEventsIgnored() {
+ return nativeAreRendererInputEventsIgnored(mNativeTabAndroid);
+ }
+
+ /**
+ * @return The {@link TabBrowserControlsOffsetHelper} for this tab.
+ */
+ public TabBrowserControlsOffsetHelper getControlsOffsetHelper() {
+ return mControlsOffsetHelper;
}
@CalledByNative
@@ -3573,4 +3541,5 @@
private native void nativeAttachDetachedTab(long nativeTabAndroid);
private native void nativeMediaDownloadInProductHelpDismissed(long nativeTabAndroid);
private native int nativeGetCurrentRenderProcessId(long nativeTabAndroid);
+ private native boolean nativeAreRendererInputEventsIgnored(long nativeTabAndroid);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
new file mode 100644
index 0000000..e8769de
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java
@@ -0,0 +1,229 @@
+// Copyright 2018 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+
+import org.chromium.base.ObserverList;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
+import org.chromium.chrome.browser.tabmodel.TabModelImpl;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+
+/**
+ * Handles browser controls offset for a Tab.
+ */
+public class TabBrowserControlsOffsetHelper {
+ /**
+ * Maximum duration for the control container slide-in animation. Note that this value matches
+ * the one in browser_controls_offset_manager.cc.
+ */
+ private static final int MAX_CONTROLS_ANIMATION_DURATION_MS = 200;
+
+ /**
+ * An interface for notification about browser controls offset updates.
+ */
+ public interface Observer {
+ /**
+ * Called when the browser controls are fully visible on screen.
+ */
+ void onBrowserControlsFullyVisible(Tab tab);
+ }
+
+ private final Tab mTab;
+ private final ObserverList<Observer> mObservers = new ObserverList<>();
+
+ private float mPreviousTopControlsOffsetY = Float.NaN;
+ private float mPreviousBottomControlsOffsetY = Float.NaN;
+ private float mPreviousContentOffsetY = Float.NaN;
+
+ /**
+ * Whether the Android browser controls offset is overridden. This handles top controls only.
+ */
+ private boolean mIsControlsOffsetOverridden;
+
+ /**
+ * The animator for slide-in animation on the Android controls.
+ */
+ private ValueAnimator mControlsAnimator;
+
+ /**
+ * @param tab The {@link Tab} that this class is associated with.
+ */
+ TabBrowserControlsOffsetHelper(Tab tab) {
+ mTab = tab;
+ }
+
+ /**
+ * @return Whether the Android browser controls offset is overridden on the browser side.
+ */
+ public boolean isControlsOffsetOverridden() {
+ return mIsControlsOffsetOverridden;
+ }
+
+ /**
+ * @return Whether the browser controls are fully visible on screen.
+ */
+ public boolean areBrowserControlsFullyVisible() {
+ final FullscreenManager manager = mTab.getFullscreenManager();
+ return Float.compare(0f, manager.getBrowserControlHiddenRatio()) == 0;
+ }
+
+ /**
+ * @param observer The observer to be added to get notifications from this class.
+ */
+ public void addObserver(Observer observer) {
+ mObservers.addObserver(observer);
+ }
+
+ /**
+ * @param observer The observer to be removed to cancel notifications from this class.
+ */
+ public void removeObserver(Observer observer) {
+ mObservers.removeObserver(observer);
+ }
+
+ /**
+ * Called when offset values related with fullscreen functionality has been changed by the
+ * compositor.
+ * @param topControlsOffsetY The Y offset of the top controls in physical pixels.
+ * {@code Float.NaN} if the value is invalid and the cached value should be used.
+ * @param bottomControlsOffsetY The Y offset of the bottom controls in physical pixels.
+ * {@code Float.NaN} if the value is invalid and the cached value should be used.
+ * @param contentOffsetY The Y offset of the content in physical pixels.
+ */
+ void onOffsetsChanged(
+ float topControlsOffsetY, float bottomControlsOffsetY, float contentOffsetY) {
+ // Cancel any animation on the Android controls and let compositor drive the offset updates.
+ resetControlsOffsetOverridden();
+
+ if (!Float.isNaN(topControlsOffsetY)) mPreviousTopControlsOffsetY = topControlsOffsetY;
+ if (!Float.isNaN(bottomControlsOffsetY)) {
+ mPreviousBottomControlsOffsetY = bottomControlsOffsetY;
+ }
+ if (!Float.isNaN(contentOffsetY)) mPreviousContentOffsetY = contentOffsetY;
+
+ if (mTab.getFullscreenManager() == null) return;
+ if (mTab.isShowingSadTab() || mTab.isNativePage()) {
+ showAndroidControls(false);
+ } else {
+ updateFullscreenManagerOffsets(false, mPreviousTopControlsOffsetY,
+ mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
+ }
+ TabModelImpl.setActualTabSwitchLatencyMetricRequired();
+ }
+
+ /**
+ * Shows the Android browser controls view.
+ * @param animate Whether a slide-in animation should be run.
+ */
+ void showAndroidControls(boolean animate) {
+ if (mTab.getFullscreenManager() == null) return;
+
+ if (animate) {
+ runBrowserDrivenShowAnimation();
+ } else {
+ updateFullscreenManagerOffsets(true, Float.NaN, Float.NaN, Float.NaN);
+ }
+ }
+
+ /**
+ * Resets the controls positions in {@link FullscreenManager} to the cached positions.
+ */
+ void resetPositions() {
+ resetControlsOffsetOverridden();
+ if (mTab.getFullscreenManager() == null) return;
+
+ boolean topOffsetsInitialized =
+ !Float.isNaN(mPreviousTopControlsOffsetY) && !Float.isNaN(mPreviousContentOffsetY);
+ boolean bottomOffsetsInitialized = !Float.isNaN(mPreviousBottomControlsOffsetY);
+ boolean isChromeHomeEnabled = FeatureUtilities.isChromeHomeEnabled();
+
+ // Make sure the dominant control offsets have been set.
+ if ((!topOffsetsInitialized && !isChromeHomeEnabled)
+ || (!bottomOffsetsInitialized && isChromeHomeEnabled)) {
+ showAndroidControls(false);
+ } else {
+ updateFullscreenManagerOffsets(false, mPreviousTopControlsOffsetY,
+ mPreviousBottomControlsOffsetY, mPreviousContentOffsetY);
+ }
+ mTab.updateFullscreenEnabledState();
+ }
+
+ /**
+ * Clears the cached browser controls positions.
+ */
+ void clearPreviousPositions() {
+ mPreviousTopControlsOffsetY = Float.NaN;
+ mPreviousBottomControlsOffsetY = Float.NaN;
+ mPreviousContentOffsetY = Float.NaN;
+ }
+
+ /**
+ * Helper method to update offsets in {@link FullscreenManager} and notify offset changes to
+ * observers if necessary.
+ */
+ private void updateFullscreenManagerOffsets(boolean toNonFullscreen, float topControlsOffset,
+ float bottomControlsOffset, float topContentOffset) {
+ final FullscreenManager manager = mTab.getFullscreenManager();
+ if (toNonFullscreen) {
+ manager.setPositionsForTabToNonFullscreen();
+ } else {
+ manager.setPositionsForTab(topControlsOffset, bottomControlsOffset, topContentOffset);
+ }
+
+ if (!areBrowserControlsFullyVisible()) return;
+ for (Observer observer : mObservers) {
+ observer.onBrowserControlsFullyVisible(mTab);
+ }
+ }
+
+ /**
+ * Helper method to cancel overridden offset on Android browser controls.
+ */
+ private void resetControlsOffsetOverridden() {
+ if (!mIsControlsOffsetOverridden) return;
+ if (mControlsAnimator != null) mControlsAnimator.cancel();
+ mIsControlsOffsetOverridden = false;
+ }
+
+ /**
+ * Helper method to run slide-in animations on the Android browser controls views.
+ */
+ private void runBrowserDrivenShowAnimation() {
+ if (mControlsAnimator != null) return;
+
+ mIsControlsOffsetOverridden = true;
+
+ final FullscreenManager manager = mTab.getFullscreenManager();
+ final float hiddenRatio = manager.getBrowserControlHiddenRatio();
+ final float topControlHeight = manager.getTopControlsHeight();
+ final float topControlOffset = hiddenRatio == 0f ? 0f : hiddenRatio * -topControlHeight;
+
+ // Set animation start value to current renderer controls offset.
+ mControlsAnimator = ValueAnimator.ofFloat(topControlOffset, 0f);
+ mControlsAnimator.setDuration(
+ (long) Math.abs(hiddenRatio * MAX_CONTROLS_ANIMATION_DURATION_MS));
+ mControlsAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mControlsAnimator = null;
+ mPreviousTopControlsOffsetY = 0f;
+ mPreviousContentOffsetY = topControlHeight;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ updateFullscreenManagerOffsets(false, topControlHeight, 0, topControlHeight);
+ }
+ });
+ mControlsAnimator.addUpdateListener((animator) -> {
+ updateFullscreenManagerOffsets(
+ false, (float) animator.getAnimatedValue(), 0, topControlHeight);
+ });
+ mControlsAnimator.start();
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
index 168d3c4..7898ebf8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabViewAndroidDelegate.java
@@ -30,12 +30,14 @@
@Override
public void onTopControlsChanged(float topControlsOffsetY, float topContentOffsetY) {
- mTab.onOffsetsChanged(topControlsOffsetY, Float.NaN, topContentOffsetY);
+ mTab.getControlsOffsetHelper().onOffsetsChanged(
+ topControlsOffsetY, Float.NaN, topContentOffsetY);
}
@Override
public void onBottomControlsChanged(float bottomControlsOffsetY, float bottomContentOffsetY) {
- mTab.onOffsetsChanged(Float.NaN, bottomControlsOffsetY, Float.NaN);
+ mTab.getControlsOffsetHelper().onOffsetsChanged(
+ Float.NaN, bottomControlsOffsetY, Float.NaN);
}
@Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
index 0359aece..6315a9b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarControlContainer.java
@@ -242,6 +242,9 @@
// Don't eat the event if we don't have a handler.
if (mSwipeHandler == null) return false;
+ // Don't react on touch events if the toolbar container is not fully visible.
+ if (!isFullyVisible()) return true;
+
// If we have ACTION_DOWN in this context, that means either no child consumed the event or
// this class is the top UI at the event position. Then, we don't need to feed the event to
// mGestureDetector here because the event is already once fed in onInterceptTouchEvent().
@@ -256,6 +259,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (!isFullyVisible()) return true;
if (mSwipeHandler == null || isOnTabStrip(event)) return false;
return mSwipeRecognizer.onTouchEvent(event);
@@ -265,6 +269,13 @@
return e.getY() <= mTabStripHeight;
}
+ /**
+ * @return Whether or not the control container is fully visible on screen.
+ */
+ private boolean isFullyVisible() {
+ return Float.compare(0f, getTranslationY()) == 0;
+ }
+
private class SwipeRecognizerImpl extends SwipeRecognizer {
public SwipeRecognizerImpl(Context context) {
super(context);
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 618415c..fd5548a 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1214,6 +1214,7 @@
"java/src/org/chromium/chrome/browser/tab/SadTabView.java",
"java/src/org/chromium/chrome/browser/tab/SadTabViewFactory.java",
"java/src/org/chromium/chrome/browser/tab/Tab.java",
+ "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java",
"java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java",
"java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java",
"java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
index 726cbe3..4b53a97 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java
@@ -77,6 +77,9 @@
mTestObserver = new TestObserver();
mActivity.getToolbarManager().getToolbarLayout().getLocationBar().addUrlFocusChangeListener(
mTestObserver);
+ TabModalPresenter presenter =
+ (TabModalPresenter) mManager.getPresenterForTest(ModalDialogManager.TAB_MODAL);
+ presenter.disableAnimationForTest();
}
@Test
diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_android.cc
index e762179..02fa6de 100644
--- a/chrome/browser/android/tab_android.cc
+++ b/chrome/browser/android/tab_android.cc
@@ -976,6 +976,14 @@
j_publisher_url);
}
+bool TabAndroid::AreRendererInputEventsIgnored(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj) {
+ content::RenderProcessHost* render_process_host =
+ web_contents()->GetMainFrame()->GetProcess();
+ return render_process_host->IgnoreInputEvents();
+}
+
void TabAndroid::ShowMediaDownloadInProductHelp(
const gfx::Rect& rect_in_frame) {
DCHECK(web_contents_);
diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h
index d854b2e..c53cdbea 100644
--- a/chrome/browser/android/tab_android.h
+++ b/chrome/browser/android/tab_android.h
@@ -303,6 +303,10 @@
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
+ bool AreRendererInputEventsIgnored(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj);
+
private:
class MediaDownloadInProductHelp;