new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -0,0 +1,838 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
+
+#include <cmath>
+#include "FrameMetrics.h"
+#include "gfxPrefs.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/UiCompositorControllerMessageTypes.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Move.h"
+#include "mozilla/Unused.h"
+
+namespace {
+
+// Internal flags and constants
+static const float ANIMATION_DURATION = 0.15f; // How many seconds the complete animation should span
+static const int32_t MOVE_TOOLBAR_DOWN = 1; // Multiplier to move the toolbar down
+static const int32_t MOVE_TOOLBAR_UP = -1; // Multiplier to move the toolbar up
+static const float SHRINK_FACTOR = 0.95f; // Amount to shrink the either the full content for small pages or the amount left
+ // See: CanHideToolbar()
+} // namespace
+
+namespace mozilla {
+namespace layers {
+
+already_AddRefed<AndroidDynamicToolbarAnimator>
+AndroidDynamicToolbarAnimator::Create()
+{
+ RefPtr<AndroidDynamicToolbarAnimator> value = new AndroidDynamicToolbarAnimator();
+ return value.forget();
+}
+
+void
+AndroidDynamicToolbarAnimator::Initialize(uint64_t aRootLayerTreeId)
+{
+ mRootLayerTreeId = aRootLayerTreeId;
+ RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+ MOZ_ASSERT(uiController);
+ uiController->RegisterAndroidDynamicToolbarAnimator(this);
+}
+
+static bool
+GetTouchY(MultiTouchInput& multiTouch, ScreenIntCoord* value)
+{
+ MOZ_ASSERT(value);
+ if (multiTouch.mTouches.Length() == 1) {
+ *value = multiTouch.mTouches[0].mScreenPoint.y;
+ return true;
+ }
+
+ return false;
+}
+
+nsEventStatus
+AndroidDynamicToolbarAnimator::ReceiveInputEvent(InputData& aEvent)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+
+ // Only process and adjust touch events. Wheel events (aka scroll events) are adjust in the NativePanZoomController
+ if (aEvent.mInputType != MULTITOUCH_INPUT) {
+ return nsEventStatus_eIgnore;
+ }
+
+ MultiTouchInput& multiTouch = aEvent.AsMultiTouchInput();
+ ScreenIntCoord currentTouch = 0;
+
+ if (mPinnedFlags || !GetTouchY(multiTouch, ¤tTouch)) {
+ TranslateTouchEvent(multiTouch);
+ return nsEventStatus_eIgnore;
+ }
+
+ // Only the return value from ProcessTouchDelta should
+ // change status to nsEventStatus_eConsumeNoDefault
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ const StaticToolbarState currentToolbarState = mToolbarState;
+ switch (multiTouch.mType) {
+ case MultiTouchInput::MULTITOUCH_START:
+ mControllerCancelTouchTracking = false;
+ mControllerStartTouch = mControllerPreviousTouch = currentTouch;
+ if (currentToolbarState == eToolbarAnimating) {
+ StopCompositorAnimation();
+ }
+ break;
+ case MultiTouchInput::MULTITOUCH_MOVE: {
+ if ((mControllerState != eAnimationStartPending) &&
+ (mControllerState != eAnimationStopPending) &&
+ (currentToolbarState != eToolbarAnimating) &&
+ !mControllerCancelTouchTracking) {
+
+ int32_t delta = currentTouch - mControllerPreviousTouch;
+ mControllerPreviousTouch = currentTouch;
+ mControllerTotalDistance += delta;
+ if (delta != 0) {
+ mControllerLastDragDirection = (delta > 0 ? MOVE_TOOLBAR_DOWN : MOVE_TOOLBAR_UP);
+ }
+ if (CanHideToolbar(delta)) {
+ const uint32_t dragThreshold = Abs(std::lround(gfxPrefs::ToolbarScrollThreshold() * mControllerCompositionHeight));
+ if ((Abs(mControllerTotalDistance.value) > dragThreshold) && (delta != 0)) {
+ mControllerDragThresholdReached = true;
+ status = ProcessTouchDelta(currentToolbarState, delta, multiTouch.mTime);
+ }
+ }
+ mControllerLastEventTimeStamp = multiTouch.mTime;
+ }
+ break;
+ }
+ case MultiTouchInput::MULTITOUCH_END:
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ HandleTouchReset(currentToolbarState, currentTouch);
+ break;
+ default:
+ break;
+ }
+
+ TranslateTouchEvent(multiTouch);
+
+ return status;
+}
+
+void
+AndroidDynamicToolbarAnimator::SetMaxToolbarHeight(ScreenIntCoord aHeight)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ UpdateControllerToolbarHeight(aHeight, aHeight);
+ mCompositorMaxToolbarHeight = aHeight;
+ UpdateCompositorToolbarHeight(aHeight);
+}
+
+void
+AndroidDynamicToolbarAnimator::SetPinned(bool aPinned, int32_t aReason)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(aReason < 32);
+ uint32_t bit = 0x01 << aReason;
+ uint32_t current = mPinnedFlags;
+ if (aPinned) {
+ mPinnedFlags = current | bit;
+ } else {
+ mPinnedFlags = current & (~bit);
+ }
+}
+
+ScreenIntCoord
+AndroidDynamicToolbarAnimator::GetMaxToolbarHeight() const
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mCompositorMaxToolbarHeight;
+}
+
+ScreenIntCoord
+AndroidDynamicToolbarAnimator::GetCurrentToolbarHeight() const
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mCompositorToolbarHeight;
+}
+
+ScreenIntCoord
+AndroidDynamicToolbarAnimator::GetCurrentSurfaceHeight() const
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mCompositorSurfaceHeight;
+}
+
+ScreenIntCoord
+AndroidDynamicToolbarAnimator::GetCompositionHeight() const
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mCompositorCompositionHeight;
+}
+
+bool
+AndroidDynamicToolbarAnimator::SetCompositionSize(ScreenIntCoord aWidth, ScreenIntCoord aHeight)
+{
+ bool sizeChanged = false;
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ if (mCompositorCompositionWidth != aWidth) {
+ mCompositorCompositionWidth = aWidth;
+ sizeChanged = true;
+ }
+ if (mCompositorCompositionHeight != aHeight) {
+ UpdateControllerCompositionHeight(aHeight);
+ mCompositorCompositionHeight = aHeight;
+ UpdateFixedLayerMargins();
+ sizeChanged = true;
+ }
+
+ return sizeChanged;
+}
+
+void
+AndroidDynamicToolbarAnimator::SetScrollingRootContent()
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ mControllerScrollingRootContent = true;
+}
+
+void
+AndroidDynamicToolbarAnimator::ToolbarAnimatorMessageFromUI(int32_t aMessage)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ switch(aMessage) {
+ case STATIC_TOOLBAR_NEEDS_UPDATE:
+ break;
+ case STATIC_TOOLBAR_READY:
+ break;
+ case TOOLBAR_HIDDEN:
+ // If the toolbar is animating, then it is already unlocked.
+ if (mToolbarState != eToolbarAnimating) {
+ mToolbarState = eToolbarUnlocked;
+ if (mCompositorAnimationDeferred) {
+ StartCompositorAnimation(mCompositorAnimationDirection, mCompositorAnimationStyle, mCompositorToolbarHeight);
+ }
+ } else {
+ // The compositor is already animating the toolbar so no need to defer.
+ mCompositorAnimationDeferred = false;
+ }
+ break;
+ case TOOLBAR_VISIBLE:
+ mToolbarState = eToolbarVisible;
+ break;
+ case TOOLBAR_SHOW:
+ break;
+ case FIRST_PAINT:
+ break;
+ case REQUEST_SHOW_TOOLBAR_IMMEDIATELY:
+ NotifyControllerPendingAnimation(MOVE_TOOLBAR_DOWN, eImmediate);
+ break;
+ case REQUEST_SHOW_TOOLBAR_ANIMATED:
+ NotifyControllerPendingAnimation(MOVE_TOOLBAR_DOWN, eAnimate);
+ break;
+ case REQUEST_HIDE_TOOLBAR_IMMEDIATELY:
+ NotifyControllerPendingAnimation(MOVE_TOOLBAR_UP, eImmediate);
+ break;
+ case REQUEST_HIDE_TOOLBAR_ANIMATED:
+ NotifyControllerPendingAnimation(MOVE_TOOLBAR_UP, eAnimate);
+ break;
+ default:
+ break;
+ }
+}
+
+bool
+AndroidDynamicToolbarAnimator::UpdateAnimation(const TimeStamp& aCurrentFrame)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ if (mToolbarState == eToolbarAnimating) {
+ bool continueAnimating = true;
+
+ if (mCompositorAnimationStyle == eImmediate) {
+ if (mCompositorAnimationDirection == MOVE_TOOLBAR_DOWN) {
+ mCompositorToolbarHeight = mCompositorMaxToolbarHeight;
+ } else if (mCompositorAnimationDirection == MOVE_TOOLBAR_UP) {
+ mCompositorToolbarHeight = 0;
+ }
+ } else if (mCompositorAnimationStyle == eAnimate) {
+ const float rate = ((float)mCompositorMaxToolbarHeight) / ANIMATION_DURATION;
+ float deltaTime = (aCurrentFrame - mCompositorAnimationStartTimeStamp).ToSeconds();
+ // This animation was started in the future!
+ if (deltaTime < 0.0f) {
+ deltaTime = 0.0f;
+ }
+ mCompositorToolbarHeight = mCompositorAnimationStartHeight + ((int32_t)(rate * deltaTime) * mCompositorAnimationDirection);
+ }
+
+ if ((mCompositorAnimationDirection == MOVE_TOOLBAR_DOWN) && (mCompositorToolbarHeight >= mCompositorMaxToolbarHeight)) {
+ continueAnimating = false;
+ mToolbarState = eToolbarVisible;
+ PostMessage(TOOLBAR_SHOW);
+ mCompositorToolbarHeight = mCompositorMaxToolbarHeight;
+ } else if ((mCompositorAnimationDirection == MOVE_TOOLBAR_UP) && (mCompositorToolbarHeight <= 0)) {
+ continueAnimating = false;
+ mToolbarState = eToolbarUnlocked;
+ mCompositorToolbarHeight = 0;
+ }
+
+ CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+ if (parent) {
+ AsyncCompositionManager* manager = parent->GetCompositionManager(nullptr);
+ if (manager) {
+ manager->SetFixedLayerMargins();
+ }
+ }
+
+ if (!continueAnimating) {
+ NotifyControllerAnimationStopped(mCompositorToolbarHeight);
+ }
+
+ return continueAnimating;
+ }
+ return false;
+}
+
+void
+AndroidDynamicToolbarAnimator::FirstPaint()
+{
+ PostMessage(FIRST_PAINT);
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateRootFrameMetrics(const FrameMetrics& aMetrics)
+{
+
+ CSSToScreenScale scale = aMetrics.GetZoom().ToScaleFactor() * ParentLayerToScreenScale(1);
+ ScreenPoint scrollOffset = aMetrics.GetScrollOffset() * scale;
+ CSSRect cssPageRect = aMetrics.GetScrollableRect();
+
+ UpdateFrameMetrics(scrollOffset, scale, cssPageRect);
+}
+
+// Layers updates are need by Robocop test which enables them
+void
+AndroidDynamicToolbarAnimator::EnableLayersUpdateNotifications(bool aEnable)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mCompositorLayersUpdateEnabled = aEnable;
+}
+
+void
+AndroidDynamicToolbarAnimator::NotifyLayersUpdated()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ if (mCompositorLayersUpdateEnabled) {
+ PostMessage(LAYERS_UPDATED);
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::AdoptToolbarPixels(ScreenIntCoord aWidth, ScreenIntCoord aHeight, mozilla::ipc::Shmem&& aMem)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mCompositorToolbarPixels = Move(aMem);
+ mCompositorToolbarPixelsWidth = aWidth;
+ mCompositorToolbarPixelsHeight = aHeight;
+ mCompositorToolbarPixelsUpdated = true;
+}
+
+Effect*
+AndroidDynamicToolbarAnimator::GetToolbarEffect(CompositorOGL* gl)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ // if the compositor has shutdown, do not create any new rendering objects.
+ if (mCompositorShutdown) {
+ return nullptr;
+ }
+
+ if (mCompositorToolbarPixelsUpdated) {
+ RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
+ mCompositorToolbarPixels.get<uint8_t>(),
+ mCompositorToolbarPixelsWidth * 4,
+ IntSize(mCompositorToolbarPixelsWidth, mCompositorToolbarPixelsHeight),
+ gfx::SurfaceFormat::B8G8R8A8);
+
+ if (!mCompositorToolbarTexture) {
+ mCompositorToolbarTexture = gl->CreateDataTextureSource();
+ mCompositorToolbarEffect = nullptr;
+ }
+
+ if (!mCompositorToolbarTexture->Update(surface)) {
+ // Upload failed!
+ mCompositorToolbarTexture = nullptr;
+ }
+
+ RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+ uiController->DeallocShmem(mCompositorToolbarPixels);
+ mCompositorToolbarPixels = mozilla::ipc::Shmem();
+ mCompositorToolbarPixelsUpdated = false;
+ if (mCompositorToolbarTexture) {
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::PostToolbarReady));
+ }
+ }
+
+ if (mCompositorToolbarTexture) {
+ if (!mCompositorToolbarEffect) {
+ mCompositorToolbarEffect = new EffectRGB(mCompositorToolbarTexture, true, SamplingFilter::LINEAR);
+ }
+
+ float ratioVisible = (float)mCompositorToolbarHeight / (float)mCompositorMaxToolbarHeight;
+ mCompositorToolbarEffect->mTextureCoords.y = 1.0f - ratioVisible;
+ mCompositorToolbarEffect->mTextureCoords.height = ratioVisible;
+ }
+
+ return mCompositorToolbarEffect.get();
+}
+
+void
+AndroidDynamicToolbarAnimator::Shutdown()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mCompositorShutdown = true;
+ mCompositorToolbarEffect = nullptr;
+ mCompositorToolbarTexture = nullptr;
+ if (mCompositorToolbarPixelsUpdated) {
+ RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+ uiController->DeallocShmem(mCompositorToolbarPixels);
+ mCompositorToolbarPixels = mozilla::ipc::Shmem();
+ }
+}
+
+AndroidDynamicToolbarAnimator::AndroidDynamicToolbarAnimator()
+ : mRootLayerTreeId(0)
+ // Read/Write Compositor Thread, Read only Controller thread
+ , mToolbarState(eToolbarVisible)
+ , mPinnedFlags(0)
+ // Controller thread only
+ , mControllerScrollingRootContent(false)
+ , mControllerDragThresholdReached(false)
+ , mControllerCancelTouchTracking(false)
+ , mControllerStartTouch(0)
+ , mControllerPreviousTouch(0)
+ , mControllerTotalDistance(0)
+ , mControllerMaxToolbarHeight(0)
+ , mControllerToolbarHeight(0)
+ , mControllerCompositionHeight(0)
+ , mControllerLastDragDirection(0)
+ , mControllerLastEventTimeStamp(0)
+ , mControllerState(eNothingPending)
+ // Compositor thread only
+ , mCompositorShutdown(false)
+ , mCompositorAnimationDeferred(false)
+ , mCompositorLayersUpdateEnabled(false)
+ , mCompositorAnimationStyle(eAnimate)
+ , mCompositorMaxToolbarHeight(0)
+ , mCompositorToolbarHeight(0)
+ , mCompositorSurfaceHeight(0)
+ , mCompositorCompositionWidth(0)
+ , mCompositorCompositionHeight(0)
+ , mCompositorAnimationDirection(0)
+ , mCompositorAnimationStartHeight(0)
+ , mCompositorToolbarPixelsUpdated(false)
+ , mCompositorToolbarPixelsWidth(0)
+ , mCompositorToolbarPixelsHeight(0)
+{}
+
+nsEventStatus
+AndroidDynamicToolbarAnimator::ProcessTouchDelta(StaticToolbarState aCurrentToolbarState, int32_t aDelta, uint32_t aTimeStamp)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ const bool tryingToHideToolbar = aDelta < 0;
+
+ if (tryingToHideToolbar && !mControllerScrollingRootContent) {
+ // This prevent the toolbar from hiding if a subframe is being scrolled up.
+ // The toolbar will always become visible regardless what is being scrolled down.
+ return status;
+ }
+
+ if (aCurrentToolbarState == eToolbarVisible) {
+ if (tryingToHideToolbar && (mControllerState != eUnlockPending)) {
+ PostMessage(STATIC_TOOLBAR_NEEDS_UPDATE);
+ mControllerState = eUnlockPending;
+ }
+ return status;
+ }
+
+ if (aCurrentToolbarState != eToolbarUnlocked) {
+ return status;
+ }
+
+ if ((mControllerState != eUnlockPending) && (mControllerState != eNothingPending)) {
+ return status;
+ }
+
+ mControllerState = eNothingPending;
+ if ((tryingToHideToolbar && (mControllerToolbarHeight > 0)) ||
+ (!tryingToHideToolbar && (mControllerToolbarHeight < mControllerMaxToolbarHeight))) {
+ mControllerToolbarHeight += aDelta;
+ if (tryingToHideToolbar && (mControllerToolbarHeight <= 0 )) {
+ mControllerToolbarHeight = 0;
+ } else if (!tryingToHideToolbar && (mControllerToolbarHeight >= mControllerMaxToolbarHeight)) {
+ mControllerToolbarHeight = mControllerMaxToolbarHeight;
+ PostMessage(TOOLBAR_SHOW);
+ mControllerState = eShowPending;
+ }
+
+ UpdateCompositorToolbarHeight(mControllerToolbarHeight);
+ RequestComposite();
+ status = nsEventStatus_eConsumeNoDefault;
+
+ uint32_t timeDelta = aTimeStamp - mControllerLastEventTimeStamp;
+ if (mControllerLastEventTimeStamp && timeDelta && aDelta) {
+ float speed = -(float)aDelta / (float)timeDelta;
+ CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+ if (parent) {
+ parent->GetAPZCTreeManager()->ProcessTouchVelocity(aTimeStamp, speed);
+ }
+ }
+ }
+
+ return status;
+}
+
+void
+AndroidDynamicToolbarAnimator::HandleTouchReset(StaticToolbarState aCurrentToolbarState, ScreenIntCoord aCurrentTouch)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ int32_t direction = mControllerLastDragDirection;;
+ mControllerLastDragDirection = 0;
+ bool isRoot = mControllerScrollingRootContent;
+ mControllerScrollingRootContent = false;
+ // If the last touch didn't have a drag direction, use start of touch to find direction
+ if (!direction) {
+ direction = ((aCurrentTouch - mControllerStartTouch) > 0 ? MOVE_TOOLBAR_DOWN : MOVE_TOOLBAR_UP);
+ // If there still isn't a direction, default to show just to be safe
+ if (!direction) {
+ direction = MOVE_TOOLBAR_DOWN;
+ }
+ }
+ bool dragThresholdReached = mControllerDragThresholdReached;
+ mControllerStartTouch = 0;
+ mControllerPreviousTouch = 0;
+ mControllerTotalDistance = 0;
+ mControllerDragThresholdReached = false;
+ mControllerLastEventTimeStamp = 0;
+
+ // Received a UI thread request to show or hide the snap shot during a touch.
+ // This overrides the touch event so just return
+ if (mControllerCancelTouchTracking) {
+ mControllerCancelTouchTracking = false;
+ return;
+ }
+
+ // Don't animate up if not scrolling root content.
+ if (!isRoot &&
+ ((direction == MOVE_TOOLBAR_UP) && (mControllerToolbarHeight == mControllerMaxToolbarHeight))) {
+ ShowToolbarIfNotVisible(aCurrentToolbarState);
+ return;
+ }
+
+ // The page is either too small or too close to the end to animate
+ if (!CanHideToolbar(direction)) {
+ if (mControllerToolbarHeight == mControllerMaxToolbarHeight) {
+ ShowToolbarIfNotVisible(aCurrentToolbarState);
+ return;
+ } else if (mControllerToolbarHeight != 0) {
+ // The snapshot is partially visible but there is not enough page
+ // to hide the snapshot so make it visible by moving it down
+ direction = MOVE_TOOLBAR_DOWN;
+ }
+ }
+
+ // This makes sure the snapshot is not left partially visible at the end of a touch.
+ if ((aCurrentToolbarState != eToolbarAnimating) && dragThresholdReached) {
+ if (((direction == MOVE_TOOLBAR_DOWN) && (mControllerToolbarHeight != mControllerMaxToolbarHeight)) ||
+ ((direction == MOVE_TOOLBAR_UP) && (mControllerToolbarHeight != 0))) {
+ StartCompositorAnimation(direction, eAnimate, mControllerToolbarHeight);
+ }
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::PostMessage(int32_t aMessage) {
+ RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+ MOZ_ASSERT(uiController);
+ // ToolbarAnimatorMessageFromCompositor may be called from any thread.
+ uiController->ToolbarAnimatorMessageFromCompositor(aMessage);
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateCompositorToolbarHeight(ScreenIntCoord aHeight)
+{
+ if (!CompositorThreadHolder::IsInCompositorThread()) {
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod<ScreenIntCoord>(this, &AndroidDynamicToolbarAnimator::UpdateCompositorToolbarHeight, aHeight));
+ return;
+ }
+
+ mCompositorToolbarHeight = aHeight;
+ UpdateFixedLayerMargins();
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateControllerToolbarHeight(ScreenIntCoord aHeight, ScreenIntCoord aMaxHeight)
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<ScreenIntCoord, ScreenIntCoord>(this, &AndroidDynamicToolbarAnimator::UpdateControllerToolbarHeight, aHeight, aMaxHeight));
+ return;
+ }
+
+ mControllerToolbarHeight = aHeight;
+ if (aMaxHeight >= 0) {
+ mControllerMaxToolbarHeight = aMaxHeight;
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateControllerCompositionHeight(ScreenIntCoord aHeight)
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<ScreenIntCoord>(this, &AndroidDynamicToolbarAnimator::UpdateControllerCompositionHeight, aHeight));
+ return;
+ }
+
+ mControllerCompositionHeight = aHeight;
+}
+
+// Ensures the margin for the fixed layers match the position of the toolbar
+void
+AndroidDynamicToolbarAnimator::UpdateFixedLayerMargins()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+ if (parent) {
+ mCompositorSurfaceHeight = parent->GetEGLSurfaceSize().height;
+ AsyncCompositionManager* manager = parent->GetCompositionManager(nullptr);
+ if (manager) {
+ manager->SetFixedLayerMargins();
+ }
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::NotifyControllerPendingAnimation(int32_t aDirection, AnimationStyle aAnimationStyle)
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<int32_t, AnimationStyle>(this, &AndroidDynamicToolbarAnimator::NotifyControllerPendingAnimation, aDirection, aAnimationStyle));
+ return;
+ }
+
+ mControllerCancelTouchTracking = true;
+
+ // If the toolbar is already where it needs to be, just abort the request.
+ if (((mControllerToolbarHeight == mControllerMaxToolbarHeight) && (aDirection == MOVE_TOOLBAR_DOWN)) ||
+ ((mControllerToolbarHeight == 0) && (aDirection == MOVE_TOOLBAR_UP))) {
+ // We received a show request but the real toolbar is hidden, so tell it to show now.
+ if ((aDirection == MOVE_TOOLBAR_DOWN) && (mToolbarState == eToolbarUnlocked)) {
+ PostMessage(TOOLBAR_SHOW);
+ }
+ return;
+ }
+
+ // NOTE: StartCompositorAnimation will set mControllerState to eAnimationStartPending
+ StartCompositorAnimation(aDirection, aAnimationStyle, mControllerToolbarHeight);
+ MOZ_ASSERT(mControllerState == eAnimationStartPending);
+}
+
+void
+AndroidDynamicToolbarAnimator::StartCompositorAnimation(int32_t aDirection, AnimationStyle aAnimationStyle, ScreenIntCoord aHeight)
+{
+
+ if (!CompositorThreadHolder::IsInCompositorThread()) {
+ mControllerState = eAnimationStartPending;
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod<int32_t, AnimationStyle, ScreenIntCoord>(
+ this, &AndroidDynamicToolbarAnimator::StartCompositorAnimation, aDirection, aAnimationStyle, aHeight));
+ return;
+ }
+
+ const StaticToolbarState currentToolbarState = mToolbarState;
+ mCompositorAnimationDirection = aDirection;
+ mCompositorAnimationStartHeight = mCompositorToolbarHeight = aHeight;
+ mCompositorAnimationStyle = aAnimationStyle;
+ // If the snapshot is not unlocked, request the UI thread update the snapshot
+ // and defer animation until it has been unlocked
+ if (currentToolbarState != eToolbarUnlocked) {
+ mCompositorAnimationDeferred = true;
+ PostMessage(STATIC_TOOLBAR_NEEDS_UPDATE);
+ } else {
+ // Toolbar is unlocked so animation may begin immediately
+ mCompositorAnimationDeferred = false;
+ mToolbarState = eToolbarAnimating;
+ // Let the controller know we starting an animation so it may clear the AnimationStartPending flag.
+ NotifyControllerAnimationStarted();
+ // Kick the compositor to start the animation
+ CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+ if (parent) {
+ mCompositorAnimationStartTimeStamp = parent->GetAPZCTreeManager()->GetFrameTime();
+ }
+ RequestComposite();
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::NotifyControllerAnimationStarted()
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::NotifyControllerAnimationStarted));
+ return;
+ }
+
+ // It is possible there was a stop request after the start request so only set to NothingPending
+ // if start is what were are still waiting for.
+ if (mControllerState == eAnimationStartPending) {
+ mControllerState = eNothingPending;
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::StopCompositorAnimation()
+{
+ if (!CompositorThreadHolder::IsInCompositorThread()) {
+ mControllerState = eAnimationStopPending;
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::StopCompositorAnimation));
+ return;
+ }
+
+ mToolbarState = eToolbarUnlocked;
+ NotifyControllerAnimationStopped(mCompositorToolbarHeight);
+}
+
+void
+AndroidDynamicToolbarAnimator::NotifyControllerAnimationStopped(ScreenIntCoord aHeight)
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<ScreenIntCoord>(this, &AndroidDynamicToolbarAnimator::NotifyControllerAnimationStopped, aHeight));
+ return;
+ }
+
+ if (mControllerState == eAnimationStopPending) {
+ mControllerState = eNothingPending;
+ } else if (mControllerState == eNothingPending) {
+// Nothing to do.
+ }
+
+ mControllerToolbarHeight = aHeight;
+}
+
+void
+AndroidDynamicToolbarAnimator::RequestComposite()
+{
+ if (!CompositorThreadHolder::IsInCompositorThread()) {
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(this, &AndroidDynamicToolbarAnimator::RequestComposite));
+ return;
+ }
+
+ CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+ if (parent) {
+ AsyncCompositionManager* manager = parent->GetCompositionManager(nullptr);
+ if (manager) {
+ manager->SetFixedLayerMargins();
+ parent->Invalidate();
+ parent->ScheduleComposition();
+ }
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::PostToolbarReady()
+{
+ RequestComposite();
+ PostMessage(STATIC_TOOLBAR_READY);
+ if (mToolbarState != eToolbarAnimating) {
+ mToolbarState = eToolbarUpdated;
+ } else {
+ // The compositor is already animating the toolbar so no need to defer.
+ mCompositorAnimationDeferred = false;
+ }
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateFrameMetrics(ScreenPoint aScrollOffset,
+ CSSToScreenScale aScale,
+ CSSRect aCssPageRect)
+{
+ if (!APZThreadUtils::IsControllerThread()) {
+ APZThreadUtils::RunOnControllerThread(NewRunnableMethod<ScreenPoint, CSSToScreenScale, CSSRect>(this, &AndroidDynamicToolbarAnimator::UpdateFrameMetrics, aScrollOffset, aScale, aCssPageRect));
+ return;
+ }
+
+ if (mControllerFrameMetrics.Update(aScrollOffset, aScale, aCssPageRect)) {
+ RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+ MOZ_ASSERT(uiController);
+ CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod<float, float, float, CSSRect>(
+ uiController, &UiCompositorControllerParent::SendRootFrameMetrics,
+ aScrollOffset.x, aScrollOffset.y, aScale.scale, aCssPageRect));
+ }
+}
+
+bool
+AndroidDynamicToolbarAnimator::CanHideToolbar(int32_t delta)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ if (delta < 0) {
+ if (((float)mCompositorSurfaceHeight >= (mControllerFrameMetrics.mPageRect.YMost() * SHRINK_FACTOR)) ||
+ ((float)mCompositorSurfaceHeight >= ((mControllerFrameMetrics.mPageRect.YMost() - mControllerFrameMetrics.mScrollOffset.y) * SHRINK_FACTOR))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+AndroidDynamicToolbarAnimator::ShowToolbarIfNotVisible(StaticToolbarState aCurrentToolbarState)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ if ((mControllerToolbarHeight == mControllerMaxToolbarHeight) &&
+ (aCurrentToolbarState != eToolbarVisible) &&
+ (mControllerState != eShowPending)) {
+ PostMessage(TOOLBAR_SHOW);
+ }
+}
+
+bool
+AndroidDynamicToolbarAnimator::FrameMetricsState::Update(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aScale,
+ const CSSRect& aCssPageRect)
+{
+ if (!FuzzyEqualsMultiplicative(aScrollOffset.x, mScrollOffset.x) ||
+ !FuzzyEqualsMultiplicative(aScrollOffset.y, mScrollOffset.y) ||
+ !FuzzyEqualsMultiplicative(aScale.scale, mScale.scale) ||
+ !FuzzyEqualsMultiplicative(aCssPageRect.width, mCssPageRect.width) ||
+ !FuzzyEqualsMultiplicative(aCssPageRect.height, mCssPageRect.height) ||
+ !FuzzyEqualsMultiplicative(aCssPageRect.x, mCssPageRect.x) ||
+ !FuzzyEqualsMultiplicative(aCssPageRect.y, mCssPageRect.y)) {
+ mScrollOffset = aScrollOffset;
+ mScale = aScale;
+ mCssPageRect = aCssPageRect;
+ mPageRect = mCssPageRect * mScale;
+ return true;
+ }
+
+ return false;
+}
+
+void
+AndroidDynamicToolbarAnimator::TranslateTouchEvent(MultiTouchInput& aTouchEvent)
+{
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+ if (mControllerToolbarHeight > 0) {
+ aTouchEvent.Translate(ScreenPoint(0.0f, -(float)mControllerToolbarHeight));
+ }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_AndroidDynamicToolbarAnimator_h_
+#define mozilla_layers_AndroidDynamicToolbarAnimator_h_
+
+#include "InputData.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/ipc/Shmem.h"
+#include "mozilla/layers/Effects.h"
+#include "mozilla/layers/TextureHost.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupports.h"
+
+namespace mozilla {
+namespace layers {
+
+struct FrameMetrics;
+class CompositorOGL;
+
+/*
+ * The AndroidDynamicToolbarAnimator is responsible for calculating the position
+ * and drawing the static snap shot of the toolbar. The animator lives in both
+ * compositor thread and controller thread. It intercepts input events in the
+ * controller thread and determines if the intercepted touch events will cause
+ * the toolbar to move or be animated. Once the proper conditions have been met,
+ * the animator requests that the UI thread send a static snapshot of the current
+ * state of the toolbar. Once the animator has received the snapshot and
+ * converted it into an OGL texture, the animator notifies the UI thread it is
+ * ready. The UI thread will then hide the real toolbar and notify the animator
+ * that it may begin moving and or animating the snapshot. The animator is
+ * responsible for rendering the snapshot until it receives a message to show the
+ * toolbar or touch events cause the snapshot to be completely visible. When the
+ * snapshot is made completely visible the animator sends a message to the UI
+ * thread to show the real toolbar and the whole process may start again.
+ * The toolbar height is in device pixels. The toolbar height will be at max height
+ * when completely visible and at 0 when completely hidden.
+ *
+ * See Bug 1335895 for more details.
+ */
+
+class AndroidDynamicToolbarAnimator {
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidDynamicToolbarAnimator);
+ static already_AddRefed<AndroidDynamicToolbarAnimator> Create();
+ void Initialize(uint64_t aRootLayerTreeId);
+ // Used to intercept events to determine if the event affects the toolbar.
+ // May apply translation to touch events if the toolbar is visible.
+ nsEventStatus ReceiveInputEvent(InputData& aEvent);
+ void SetMaxToolbarHeight(ScreenIntCoord aHeight);
+ // When a pinned reason is set to true, the animator will prevent
+ // touch events from altering the height of the toolbar. All pinned
+ // reasons must be cleared before touch events will affect the toolbar.
+ // Animation requests from the UI thread are still honored even if any
+ // pin reason is set. This allows the UI thread to pin the toolbar for
+ // full screen and then request the animator hide the toolbar.
+ void SetPinned(bool aPinned, int32_t aReason);
+ // returns maximum number of Y device pixels the toolbar will cover when fully visible.
+ ScreenIntCoord GetMaxToolbarHeight() const;
+ // returns the current number of Y device pixels the toolbar is currently showing.
+ ScreenIntCoord GetCurrentToolbarHeight() const;
+ // returns the height in device pixels of the current Android surface used to display content and the toolbar.
+ // This will only change when the surface provided by the system actually changes size such as when
+ // the device is rotated or the virtual keyboard is made visible.
+ ScreenIntCoord GetCurrentSurfaceHeight() const;
+ // This is the height in device pixels of the root document's content. While the toolbar is being hidden or
+ // shown, the content may extend beyond the bottom of the surface until the toolbar is completely
+ // visible or hidden.
+ ScreenIntCoord GetCompositionHeight() const;
+ // Returns true if the composition size has changed from the last time it was set.
+ bool SetCompositionSize(ScreenIntCoord aWidth, ScreenIntCoord aHeight);
+ // Called to signal that root content is being scrolled. This prevents sub scroll frames from
+ // affecting the toolbar when being scrolled up. The idea is a scrolling down will always
+ // show the toolbar while scrolling up will only hide the toolbar if it is the root content
+ // being scrolled.
+ void SetScrollingRootContent();
+ void ToolbarAnimatorMessageFromUI(int32_t aMessage);
+ // Returns true if the animation if the animation will continue and false if it has completed.
+ bool UpdateAnimation(const TimeStamp& aCurrentFrame);
+ // Called to signify the first paint has occurred.
+ void FirstPaint();
+ // Called whenever the root document's FrameMetrics have reached a steady state.
+ void UpdateRootFrameMetrics(const FrameMetrics& aMetrics);
+ // When aEnable is set to true, it informs the animator that the UI thread expects to
+ // be notified when the layer tree has been updated. Enabled currently by robocop tests.
+ void EnableLayersUpdateNotifications(bool aEnable);
+ // Called when a layer has been updated so the UI thread may be notified if necessary.
+ void NotifyLayersUpdated();
+ // Adopts the Shmem containing the toolbar snapshot sent from the UI thread.
+ // The AndroidDynamicToolbarAnimator is responsible for deallocating the Shmem when
+ // it is done being used.
+ void AdoptToolbarPixels(ScreenIntCoord aWidth, ScreenIntCoord aHeight, mozilla::ipc::Shmem&& aMem);
+ // Returns the Effect object used by the compositor to render the toolbar snapshot.
+ Effect* GetToolbarEffect(CompositorOGL* gl);
+ void Shutdown();
+
+protected:
+ enum StaticToolbarState {
+ eToolbarVisible,
+ eToolbarUpdated,
+ eToolbarUnlocked,
+ eToolbarAnimating
+ };
+ enum ControllerThreadState {
+ eNothingPending,
+ eShowPending,
+ eUnlockPending,
+ eAnimationStartPending,
+ eAnimationStopPending
+ };
+ enum AnimationStyle {
+ eImmediate,
+ eAnimate
+ };
+
+ AndroidDynamicToolbarAnimator();
+ ~AndroidDynamicToolbarAnimator(){}
+ nsEventStatus ProcessTouchDelta(StaticToolbarState aCurrentToolbarState, int32_t aDelta, uint32_t aTimeStamp);
+ // Called when a touch ends
+ void HandleTouchReset(StaticToolbarState aCurrentToolbarState, ScreenIntCoord aCurrentTouch);
+ // Sends a message to the UI thread. May be called from any thread
+ void PostMessage(int32_t aMessage);
+ void UpdateCompositorToolbarHeight(ScreenIntCoord aHeight);
+ void UpdateControllerToolbarHeight(ScreenIntCoord aHeight, ScreenIntCoord aMaxHeight = -1);
+ void UpdateControllerCompositionHeight(ScreenIntCoord aHeight);
+ void UpdateFixedLayerMargins();
+ void NotifyControllerPendingAnimation(int32_t aDirection, AnimationStyle aStyle);
+ void StartCompositorAnimation(int32_t aDirection, AnimationStyle aStyle, ScreenIntCoord aHeight);
+ void NotifyControllerAnimationStarted();
+ void StopCompositorAnimation();
+ void NotifyControllerAnimationStopped(ScreenIntCoord aHeight);
+ void RequestComposite();
+ void PostToolbarReady();
+ void UpdateFrameMetrics(ScreenPoint aScrollOffset,
+ CSSToScreenScale aScale,
+ CSSRect aCssPageRect);
+ bool CanHideToolbar(int32_t delta);
+ void ShowToolbarIfNotVisible(StaticToolbarState aCurrentToolbarState);
+ void TranslateTouchEvent(MultiTouchInput& aTouchEvent);
+
+ // Read only Compositor and Controller threads after Initialize()
+ uint64_t mRootLayerTreeId;
+
+ // Read/Write Compositor Thread, Read only Controller thread
+ Atomic<StaticToolbarState> mToolbarState; // Current toolbar state.
+ Atomic<uint32_t> mPinnedFlags; // The toolbar should not be moved or animated unless no flags are set
+
+ // Controller thread only
+ bool mControllerScrollingRootContent; // Set to true when the root content is being scrolled
+ bool mControllerDragThresholdReached; // Set to true when the drag threshold has been passed in a single touch
+ bool mControllerCancelTouchTracking; // Set to true when the UI thread requests the toolbar be made visible
+ ScreenIntCoord mControllerStartTouch; // The Y position where the touch started
+ ScreenIntCoord mControllerPreviousTouch; // The previous Y position of the touch
+ ScreenIntCoord mControllerTotalDistance; // Total distance travel during the current touch
+ ScreenIntCoord mControllerMaxToolbarHeight; // Max height of the toolbar
+ ScreenIntCoord mControllerToolbarHeight; // Current height of the toolbar
+ ScreenIntCoord mControllerCompositionHeight; // Current height of the visible page
+ int32_t mControllerLastDragDirection; // Direction of movement of the previous touch move event
+ uint32_t mControllerLastEventTimeStamp; // Time stamp for the previous touch event received
+ ControllerThreadState mControllerState; // Contains the expected pending state of the mToolbarState
+
+ // Contains the values from the last steady state root content FrameMetrics
+ struct FrameMetricsState {
+ ScreenPoint mScrollOffset;
+ CSSToScreenScale mScale;
+ CSSRect mCssPageRect;
+ ScreenRect mPageRect;
+
+ // Returns true if any of the values have changed.
+ bool Update(const ScreenPoint& aScrollOffset,
+ const CSSToScreenScale& aScale,
+ const CSSRect& aCssPageRect);
+ };
+
+ // Controller thread only
+ FrameMetricsState mControllerFrameMetrics;
+
+ // Compositor thread only
+ bool mCompositorShutdown;
+ bool mCompositorAnimationDeferred; // An animation has been deferred until the controller thread has been notified
+ bool mCompositorLayersUpdateEnabled; // Flag set to true when the UI thread is expecting to be notified when a layer has been updated
+ AnimationStyle mCompositorAnimationStyle; // Set to true when the snap should be immediately hidden or shown in the animation update
+ ScreenIntCoord mCompositorMaxToolbarHeight; // Should contain the same value as mControllerMaxToolbarHeight
+ ScreenIntCoord mCompositorToolbarHeight; // This value is only updated by the compositor thread when the mToolbarState == ToolbarAnimating
+ ScreenIntCoord mCompositorSurfaceHeight; // Current height of the render surface
+ ScreenIntCoord mCompositorCompositionWidth; // Current width of the visible page
+ ScreenIntCoord mCompositorCompositionHeight; // Current height of the visible page
+ int32_t mCompositorAnimationDirection; // Direction the snapshot should be animated
+ ScreenIntCoord mCompositorAnimationStartHeight; // The height of the snapshot at the start of an animation
+ bool mCompositorToolbarPixelsUpdated; // Set to true when the snapshot needs to be updated with new pixels
+ ScreenIntCoord mCompositorToolbarPixelsWidth; // Width of the received toolbar pixels
+ ScreenIntCoord mCompositorToolbarPixelsHeight; // Height of the received toolbar pixels
+ mozilla::ipc::Shmem mCompositorToolbarPixels; // Shared memory contain the updated snapshot pixels used to create the OGL texture
+ RefPtr<DataTextureSource> mCompositorToolbarTexture; // The OGL texture used to render the snapshot in the compositor
+ RefPtr<EffectRGB> mCompositorToolbarEffect; // Effect used to render the snapshot in the compositor
+ TimeStamp mCompositorAnimationStartTimeStamp; // Time stamp when the current animation started
+};
+
+} // namespace layers
+} // namespace mozilla
+#endif // mozilla_layers_AndroidDynamicToolbarAnimator_h_