Bug 1335895 - part 12: Add AndroidDynamicToolbarAnimator r=kats,botond draft
authorRandall Barker <rbarker@mozilla.com>
Mon, 27 Mar 2017 10:31:47 -0700
changeset 565561 0e180ba567d7347ebbd1d7f5d5bf9769616891a8
parent 565560 dd84c1777f8f826a4e66dc090ee373c86b5a2035
child 565562 3a33e8fcb91fce8eb2632dbc4009114f1f33c2a7
push id54904
push userbmo:rbarker@mozilla.com
push dateThu, 20 Apr 2017 02:39:58 +0000
reviewerskats, botond
bugs1335895
milestone55.0a1
Bug 1335895 - part 12: Add AndroidDynamicToolbarAnimator r=kats,botond
gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
gfx/layers/moz.build
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -0,0 +1,863 @@
+/* -*- 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: IsEnoughPageToHideToolbar()
+} // namespace
+
+namespace mozilla {
+namespace layers {
+
+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)
+  , mControllerSurfaceHeight(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)
+  , mCompositorAnimationDirection(0)
+  , mCompositorAnimationStartHeight(0)
+{}
+
+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 adjusted in the NativePanZoomController
+  if (aEvent.mInputType != MULTITOUCH_INPUT) {
+    return nsEventStatus_eIgnore;
+  }
+
+  MultiTouchInput& multiTouch = aEvent.AsMultiTouchInput();
+  ScreenIntCoord currentTouch = 0;
+
+  if (mPinnedFlags || !GetTouchY(multiTouch, &currentTouch)) {
+    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) {
+
+      ScreenIntCoord delta = currentTouch - mControllerPreviousTouch;
+      mControllerPreviousTouch = currentTouch;
+      mControllerTotalDistance += delta;
+      if (delta != 0) {
+        mControllerLastDragDirection = (delta > 0 ? MOVE_TOOLBAR_DOWN : MOVE_TOOLBAR_UP);
+      }
+      if (IsEnoughPageToHideToolbar(delta)) {
+        // NOTE: gfxPrefs::ToolbarScrollThreshold() returns a percentage as an intt32_t. So multiply it by 0.01f to convert.
+        const uint32_t dragThreshold = Abs(std::lround(0.01f * 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:
+    HandleTouchEnd(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 mCompositorCompositionSize.height;
+}
+
+bool
+AndroidDynamicToolbarAnimator::SetCompositionSize(ScreenIntSize aSize)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  if (mCompositorCompositionSize == aSize) {
+    return false;
+  }
+
+  ScreenIntCoord prevHeight = mCompositorCompositionSize.height;
+  mCompositorCompositionSize = aSize;
+
+  if (prevHeight != aSize.height) {
+    UpdateControllerCompositionHeight(aSize.height);
+    UpdateFixedLayerMargins();
+  }
+
+  return true;
+}
+
+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) {
+    return false;
+  }
+
+  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->SetFixedLayerMarginsBottom(GetFixedLayerMarginsBottom());
+    }
+  }
+
+  if (!continueAnimating) {
+    NotifyControllerAnimationStopped(mCompositorToolbarHeight);
+  }
+
+  return continueAnimating;
+}
+
+void
+AndroidDynamicToolbarAnimator::FirstPaint()
+{
+  PostMessage(FIRST_PAINT);
+}
+
+void
+AndroidDynamicToolbarAnimator::UpdateRootFrameMetrics(const FrameMetrics& aMetrics)
+{
+  CSSToScreenScale scale = ViewTargetAs<ScreenPixel>(aMetrics.GetZoom().ToScaleFactor(),
+                                                     PixelCastJustification::ScreenIsParentLayerForRoot);
+  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(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  mCompositorToolbarPixels = Some(Move(aMem));
+  mCompositorToolbarPixelsSize = aSize;
+}
+
+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 (mCompositorToolbarPixels) {
+    RefPtr<DataSourceSurface> surface = Factory::CreateWrappingDataSourceSurface(
+        mCompositorToolbarPixels.ref().get<uint8_t>(),
+        mCompositorToolbarPixelsSize.width * 4,
+        IntSize(mCompositorToolbarPixelsSize.width, mCompositorToolbarPixelsSize.height),
+        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.ref());
+    mCompositorToolbarPixels.reset();
+    // Send notification that texture is ready after the current composition has completed.
+    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 (mCompositorToolbarPixels) {
+    RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeId);
+    uiController->DeallocShmem(mCompositorToolbarPixels.ref());
+    mCompositorToolbarPixels.reset();
+  }
+}
+
+nsEventStatus
+AndroidDynamicToolbarAnimator::ProcessTouchDelta(StaticToolbarState aCurrentToolbarState, ScreenIntCoord 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))) {
+    ScreenIntCoord deltaRemainder = 0;
+    mControllerToolbarHeight += aDelta;
+    if (tryingToHideToolbar && (mControllerToolbarHeight <= 0 )) {
+      deltaRemainder = mControllerToolbarHeight;
+      mControllerToolbarHeight = 0;
+    } else if (!tryingToHideToolbar && (mControllerToolbarHeight >= mControllerMaxToolbarHeight)) {
+      deltaRemainder = mControllerToolbarHeight - mControllerMaxToolbarHeight;
+      mControllerToolbarHeight = mControllerMaxToolbarHeight;
+      PostMessage(TOOLBAR_SHOW);
+      mControllerState = eShowPending;
+    }
+
+    UpdateCompositorToolbarHeight(mControllerToolbarHeight);
+    RequestComposite();
+    // If there was no delta left over, the event was completely consumed.
+    if (deltaRemainder == 0) {
+      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::HandleTouchEnd(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 snapshot 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 (!IsEnoughPageToHideToolbar(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::UpdateControllerSurfaceHeight(ScreenIntCoord aHeight)
+{
+  if (!APZThreadUtils::IsControllerThread()) {
+    APZThreadUtils::RunOnControllerThread(NewRunnableMethod<ScreenIntCoord>(this, &AndroidDynamicToolbarAnimator::UpdateControllerSurfaceHeight, aHeight));
+    return;
+  }
+
+  mControllerSurfaceHeight = aHeight;
+}
+
+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) {
+    ScreenIntCoord surfaceHeight = parent->GetEGLSurfaceSize().height;
+    if (surfaceHeight != mCompositorSurfaceHeight) {
+      mCompositorSurfaceHeight = surfaceHeight;
+      UpdateControllerSurfaceHeight(mCompositorSurfaceHeight);
+    }
+    AsyncCompositionManager* manager = parent->GetCompositionManager(nullptr);
+    if (manager) {
+      manager->SetFixedLayerMarginsBottom(GetFixedLayerMarginsBottom());
+    }
+  }
+}
+
+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;
+  }
+
+  MOZ_ASSERT(aDirection == MOVE_TOOLBAR_UP || aDirection == MOVE_TOOLBAR_DOWN);
+
+  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;
+  }
+
+  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->SetFixedLayerMarginsBottom(GetFixedLayerMarginsBottom());
+      parent->Invalidate();
+      parent->ScheduleComposition();
+    }
+  }
+}
+
+void
+AndroidDynamicToolbarAnimator::PostToolbarReady()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  RequestComposite();
+  // Notify the UI thread the static toolbar is being rendered so the real
+  // toolbar needs to be hidden. Once the TOOLBAR_HIDDEN message is
+  // received, a pending animation may start or the toolbar snapshot may be
+  // translated.
+  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<ScreenPoint, CSSToScreenScale, CSSRect>(
+                                               uiController, &UiCompositorControllerParent::SendRootFrameMetrics,
+                                               aScrollOffset, aScale, aCssPageRect));
+  }
+}
+
+bool
+AndroidDynamicToolbarAnimator::IsEnoughPageToHideToolbar(ScreenIntCoord delta)
+{
+  MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+  // The toolbar will only hide if dragging up so ignore positive deltas from dragging down.
+  if (delta >= 0) {
+    return true;
+  }
+
+  // if the page is 1) too small or 2) too close to the bottom, then the toolbar can not be hidden
+  if (((float)mControllerSurfaceHeight >= (mControllerFrameMetrics.mPageRect.YMost() * SHRINK_FACTOR)) ||
+      ((float)mControllerSurfaceHeight >= ((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));
+  }
+}
+
+ScreenIntCoord
+AndroidDynamicToolbarAnimator::GetFixedLayerMarginsBottom()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  return mCompositorToolbarHeight - (mCompositorSurfaceHeight - mCompositorCompositionSize.height);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
@@ -0,0 +1,211 @@
+/* -*- 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/Maybe.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 snapshot 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 is unlocked and may begin translating 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 locks the static
+ * toolbar and sends a message to the UI thread to show the real toolbar and the
+ * whole process may start again. The toolbar height is in screen pixels. The
+ * toolbar height will be at max height when completely visible and at 0 when
+ * completely hidden. The toolbar is only locked when it is completely visible.
+ * The animator must ask for an update of the toolbar snapshot and that the real
+ * toolbar be hidden in order to unlock the static snapshot and begin translating it.
+ *
+ * See Bug 1335895 for more details.
+ */
+
+class AndroidDynamicToolbarAnimator {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidDynamicToolbarAnimator);
+  AndroidDynamicToolbarAnimator();
+  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.
+  // Returns nsEventStatus_eIgnore when the event is not consumed and
+  // nsEventStatus_eConsumeNoDefault when the event was used to translate the
+  // toolbar.
+  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(ScreenIntSize aSize);
+  // 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 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(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize);
+  // 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(){}
+  nsEventStatus ProcessTouchDelta(StaticToolbarState aCurrentToolbarState, ScreenIntCoord aDelta, uint32_t aTimeStamp);
+  // Called when a touch ends
+  void HandleTouchEnd(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 UpdateControllerSurfaceHeight(ScreenIntCoord aHeight);
+  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 IsEnoughPageToHideToolbar(ScreenIntCoord delta);
+  void ShowToolbarIfNotVisible(StaticToolbarState aCurrentToolbarState);
+  void TranslateTouchEvent(MultiTouchInput& aTouchEvent);
+  ScreenIntCoord GetFixedLayerMarginsBottom();
+
+  // 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 drag
+  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 mControllerSurfaceHeight;     // Current height of the render surface
+  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 toolbar is unlocked
+  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
+  ScreenIntSize  mCompositorCompositionSize;      // Current size 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
+  ScreenIntSize mCompositorToolbarPixelsSize;     // Size of the received toolbar pixels
+  Maybe<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_
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -241,16 +241,20 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
         'ipc/ShadowLayerUtilsMac.cpp',
         'MacIOSurfaceHelpers.cpp',
         'MacIOSurfaceImage.cpp',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     UNIFIED_SOURCES += [
         'apz/src/AndroidAPZ.cpp',
+        'apz/src/AndroidDynamicToolbarAnimator.cpp',
+    ]
+    EXPORTS.mozilla.layers += [
+        'apz/src/AndroidDynamicToolbarAnimator.h',
     ]
 
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
     'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/Axis.cpp',