Bug 1335895 - Android GeckoView Dynamic Toolbar Version 3 r=botond,dvander,jchen,kats
authorRandall Barker <rbarker@mozilla.com>
Wed, 05 Apr 2017 15:42:50 -0700
changeset 402357 81de9d1439b0e352729142f6aa2914674073da03
parent 402356 030533bed090ea932ca32d6af60ba22bde4345a8
child 402358 a2c9cf2db1e41f369fe7e1edf854317a9bb4ed23
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond, dvander, jchen, kats
bugs1335895
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1335895 - Android GeckoView Dynamic Toolbar Version 3 r=botond,dvander,jchen,kats This version of the Dynamic Toolbar moves the animation of the toolbar from the Android UI thread to the compositor thread. All animation for showing and hiding the toolbar are done with the compositor and a static snapshot of the real toolbar. MozReview-Commit-ID: BCe8zpbkWQt
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
gfx/ipc/CompositorSession.h
gfx/ipc/GPUParent.cpp
gfx/ipc/GPUParent.h
gfx/ipc/GPUProcessManager.cpp
gfx/ipc/GPUProcessManager.h
gfx/ipc/InProcessCompositorSession.cpp
gfx/ipc/PGPU.ipdl
gfx/ipc/RemoteCompositorSession.cpp
gfx/layers/apz/public/GeckoContentController.h
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientLayerManager.h
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/composite/AsyncCompositionManager.h
gfx/layers/composite/LayerManagerComposite.cpp
gfx/layers/composite/LayerManagerComposite.h
gfx/layers/ipc/APZChild.cpp
gfx/layers/ipc/APZChild.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/layers/ipc/CompositorBridgeParent.h
gfx/layers/ipc/LayersMessages.ipdlh
gfx/layers/ipc/PAPZ.ipdl
gfx/layers/ipc/PUiCompositorController.ipdl
gfx/layers/ipc/RemoteContentController.cpp
gfx/layers/ipc/RemoteContentController.h
gfx/layers/ipc/ShadowLayers.cpp
gfx/layers/ipc/ShadowLayers.h
gfx/layers/ipc/UiCompositorControllerChild.cpp
gfx/layers/ipc/UiCompositorControllerChild.h
gfx/layers/ipc/UiCompositorControllerMessageTypes.h
gfx/layers/ipc/UiCompositorControllerParent.cpp
gfx/layers/ipc/UiCompositorControllerParent.h
gfx/layers/moz.build
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPrefs.h
layout/base/PositionedEventTargeting.cpp
mobile/android/app/mobile.js
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/Tabs.java
mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java
mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java
mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/resources/drawable/toolbar_grey_round.xml
mobile/android/base/resources/layout/gecko_app.xml
mobile/android/base/resources/layout/zoomed_view.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/base/resources/xml/preferences_accessibility.xml
mobile/android/base/strings.xml.in
mobile/android/chrome/content/browser.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomTarget.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
widget/InputData.cpp
widget/InputData.h
widget/android/AndroidCompositorWidget.cpp
widget/android/AndroidCompositorWidget.h
widget/android/AndroidContentController.cpp
widget/android/AndroidContentController.h
widget/android/GeneratedJNINatives.h
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/fennec/FennecJNINatives.h
widget/android/fennec/FennecJNIWrappers.cpp
widget/android/fennec/FennecJNIWrappers.h
widget/android/fennec/ThumbnailHelper.h
widget/android/jni/Refs.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
widget/nsBaseWidget.h
widget/nsIWidget.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4147,31 +4147,16 @@ nsDOMWindowUtils::HasRuleProcessorUsedBy
     return NS_ERROR_FAILURE;
   }
 
   return presShell->HasRuleProcessorUsedByMultipleStyleSets(aSheetType,
                                                             aRetVal);
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetNextPaintSyncId(int32_t aSyncId)
-{
-  if (nsIWidget* widget = GetWidget()) {
-    RefPtr<LayerManager> lm = widget->GetLayerManager();
-    if (lm && lm->AsClientLayerManager()) {
-      lm->AsClientLayerManager()->SetNextPaintSyncId(aSyncId);
-      return NS_OK;
-    }
-  }
-
-  NS_WARNING("Paint sync id could not be set on the ClientLayerManager");
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsDOMWindowUtils::RespectDisplayPortSuppression(bool aEnabled)
 {
   nsCOMPtr<nsIPresShell> shell(GetPresShell());
   APZCCallbackHelper::RespectDisplayPortSuppression(aEnabled, shell);
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1951,18 +1951,16 @@ interface nsIDOMWindowUtils : nsISupport
    * counters and an image node with an SVG source will flush the SVG
    * document's counters.  Normally, use counters are flushed to telemetry
    * upon document destruction, but as document destruction is somewhat
    * non-deterministic, we have this method here for more determinism when
    * running tests.
    */
   void forceUseCounterFlush(in nsIDOMNode aNode);
 
-  void setNextPaintSyncId(in long aSyncId);
-
   /**
    * Enable or disable displayport suppression. This is intended to be used by
    * testing code, to provide more deterministic behaviour over the displayport
    * suppression during tests. Note that this updates a flag, so whatever value
    * was last provided is what will be used.
    */
   void respectDisplayPortSuppression(in boolean aEnabled);
 
--- a/gfx/ipc/CompositorSession.h
+++ b/gfx/ipc/CompositorSession.h
@@ -5,16 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef _include_mozilla_gfx_ipc_CompositorSession_h_
 #define _include_mozilla_gfx_ipc_CompositorSession_h_
 
 #include "base/basictypes.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "nsISupportsImpl.h"
+#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/layers/UiCompositorControllerChild.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
 
 class nsIWidget;
 
 namespace mozilla {
 namespace widget {
 class CompositorWidget;
 class CompositorWidgetDelegate;
 } // namespace widget
@@ -67,27 +70,40 @@ public:
     return mCompositorWidgetDelegate;
   }
 
   // Return the id of the root layer tree.
   uint64_t RootLayerTreeId() const {
     return mRootLayerTreeId;
   }
 
+#if defined(MOZ_WIDGET_ANDROID)
+  // Set the UiCompositorControllerChild after Session creation so the Session constructor
+  // doesn't get mucked up for other platforms.
+  void SetUiCompositorControllerChild(RefPtr<UiCompositorControllerChild> aUiController) {
+    mUiCompositorControllerChild = aUiController;
+  }
+
+  RefPtr<UiCompositorControllerChild> GetUiCompositorControllerChild() {
+    return mUiCompositorControllerChild;
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
 protected:
   CompositorSession(CompositorWidgetDelegate* aDelegate,
                     CompositorBridgeChild* aChild,
                     const uint64_t& aRootLayerTreeId);
   virtual ~CompositorSession();
 
 protected:
   CompositorWidgetDelegate* mCompositorWidgetDelegate;
   RefPtr<CompositorBridgeChild> mCompositorBridgeChild;
   uint64_t mRootLayerTreeId;
-
+#if defined(MOZ_WIDGET_ANDROID)
+  RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild;
+#endif // defined(MOZ_WIDGET_ANDROID)
 private:
   DISALLOW_COPY_AND_ASSIGN(CompositorSession);
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // _include_mozilla_gfx_ipc_CompositorSession_h_
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -229,19 +229,19 @@ GPUParent::RecvInitImageBridge(Endpoint<
 mozilla::ipc::IPCResult
 GPUParent::RecvInitVRManager(Endpoint<PVRManagerParent>&& aEndpoint)
 {
   VRManagerParent::CreateForGPUProcess(Move(aEndpoint));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-GPUParent::RecvInitUiCompositorController(Endpoint<PUiCompositorControllerParent>&& aEndpoint)
+GPUParent::RecvInitUiCompositorController(const uint64_t& aRootLayerTreeId, Endpoint<PUiCompositorControllerParent>&& aEndpoint)
 {
-  UiCompositorControllerParent::Start(Move(aEndpoint));
+  UiCompositorControllerParent::Start(aRootLayerTreeId, Move(aEndpoint));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 GPUParent::RecvUpdatePref(const GfxPrefSetting& setting)
 {
   gfxPrefs::Pref* pref = gfxPrefs::all()[setting.index()];
   pref->SetCachedValue(setting.value());
--- a/gfx/ipc/GPUParent.h
+++ b/gfx/ipc/GPUParent.h
@@ -32,17 +32,17 @@ public:
 
   mozilla::ipc::IPCResult RecvInit(nsTArray<GfxPrefSetting>&& prefs,
                                    nsTArray<GfxVarUpdate>&& vars,
                                    const DevicePrefs& devicePrefs,
                                    nsTArray<LayerTreeIdMapping>&& mappings) override;
   mozilla::ipc::IPCResult RecvInitVsyncBridge(Endpoint<PVsyncBridgeParent>&& aVsyncEndpoint) override;
   mozilla::ipc::IPCResult RecvInitImageBridge(Endpoint<PImageBridgeParent>&& aEndpoint) override;
   mozilla::ipc::IPCResult RecvInitVRManager(Endpoint<PVRManagerParent>&& aEndpoint) override;
-  mozilla::ipc::IPCResult RecvInitUiCompositorController(Endpoint<PUiCompositorControllerParent>&& aEndpoint) override;
+  mozilla::ipc::IPCResult RecvInitUiCompositorController(const uint64_t& aRootLayerTreeId, Endpoint<PUiCompositorControllerParent>&& aEndpoint) override;
   mozilla::ipc::IPCResult RecvUpdatePref(const GfxPrefSetting& pref) override;
   mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref) override;
   mozilla::ipc::IPCResult RecvNewWidgetCompositor(
       Endpoint<PCompositorBridgeParent>&& aEndpoint,
       const CSSToLayoutDeviceScale& aScale,
       const TimeDuration& aVsyncRate,
       const CompositorOptions& aOptions,
       const bool& aUseExternalSurface,
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -249,48 +249,43 @@ GPUProcessManager::EnsureVRManager()
     return;
   }
 
   mGPUChild->SendInitVRManager(Move(parentPipe));
   VRManagerChild::InitWithGPUProcess(Move(childPipe));
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
-void
-GPUProcessManager::EnsureUiCompositorController()
+already_AddRefed<UiCompositorControllerChild>
+GPUProcessManager::CreateUiCompositorController(nsBaseWidget* aWidget, const uint64_t aId)
 {
-  if (UiCompositorControllerChild::IsInitialized()) {
-    return;
-  }
-
-  RefPtr<nsThread> uiThread;
-
-  uiThread = GetAndroidUiThread();
-
-  MOZ_ASSERT(uiThread);
+  RefPtr<UiCompositorControllerChild> result;
 
   if (!EnsureGPUReady()) {
-    UiCompositorControllerChild::InitSameProcess(uiThread);
-    return;
-  }
+    result = UiCompositorControllerChild::CreateForSameProcess(aId);
+  } else {
+    ipc::Endpoint<PUiCompositorControllerParent> parentPipe;
+    ipc::Endpoint<PUiCompositorControllerChild> childPipe;
+    nsresult rv = PUiCompositorController::CreateEndpoints(
+      mGPUChild->OtherPid(),
+      base::GetCurrentProcId(),
+      &parentPipe,
+      &childPipe);
+    if (NS_FAILED(rv)) {
+      DisableGPUProcess("Failed to create PUiCompositorController endpoints");
+      return nullptr;
+    }
 
-  ipc::Endpoint<PUiCompositorControllerParent> parentPipe;
-  ipc::Endpoint<PUiCompositorControllerChild> childPipe;
-  nsresult rv = PUiCompositorController::CreateEndpoints(
-    mGPUChild->OtherPid(),
-    base::GetCurrentProcId(),
-    &parentPipe,
-    &childPipe);
-  if (NS_FAILED(rv)) {
-    DisableGPUProcess("Failed to create PUiCompositorController endpoints");
-    return;
+    mGPUChild->SendInitUiCompositorController(aId, Move(parentPipe));
+    result = UiCompositorControllerChild::CreateForGPUProcess(mProcessToken, Move(childPipe));
   }
-
-  mGPUChild->SendInitUiCompositorController(Move(parentPipe));
-  UiCompositorControllerChild::InitWithGPUProcess(uiThread, mProcessToken, Move(childPipe));
+  if (result) {
+    result->SetBaseWidget(aWidget);
+  }
+  return result.forget();
 }
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 void
 GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost)
 {
   MOZ_ASSERT(mProcess && mProcess == aHost);
 
@@ -545,19 +540,16 @@ GPUProcessManager::DestroyProcess()
   mProcess->Shutdown();
   mProcessToken = 0;
   mProcess = nullptr;
   mGPUChild = nullptr;
   if (mVsyncBridge) {
     mVsyncBridge->Close();
     mVsyncBridge = nullptr;
   }
-#if defined(MOZ_WIDGET_ANDROID)
-  UiCompositorControllerChild::Shutdown();
-#endif // defined(MOZ_WIDGET_ANDROID)
 
 #ifdef MOZ_CRASHREPORTER
   CrashReporter::AnnotateCrashReport(
     NS_LITERAL_CSTRING("GPUProcessStatus"),
     NS_LITERAL_CSTRING("Destroyed"));
 #endif
 }
 
@@ -568,46 +560,55 @@ GPUProcessManager::CreateTopLevelComposi
                                             const CompositorOptions& aOptions,
                                             bool aUseExternalSurfaceSize,
                                             const gfx::IntSize& aSurfaceSize)
 {
   uint64_t layerTreeId = AllocateLayerTreeId();
 
   EnsureImageBridgeChild();
   EnsureVRManager();
-#if defined(MOZ_WIDGET_ANDROID)
-  EnsureUiCompositorController();
-#endif // defined(MOZ_WIDGET_ANDROID)
+
+  RefPtr<CompositorSession> session;
 
   if (EnsureGPUReady()) {
-    RefPtr<CompositorSession> session = CreateRemoteSession(
+    session = CreateRemoteSession(
       aWidget,
       aLayerManager,
       layerTreeId,
       aScale,
       aOptions,
       aUseExternalSurfaceSize,
       aSurfaceSize);
-    if (session) {
-      return session;
+    if (!session) {
+      // We couldn't create a remote compositor, so abort the process.
+      DisableGPUProcess("Failed to create remote compositor");
     }
-
-    // We couldn't create a remote compositor, so abort the process.
-    DisableGPUProcess("Failed to create remote compositor");
   }
 
-  return InProcessCompositorSession::Create(
-    aWidget,
-    aLayerManager,
-    layerTreeId,
-    aScale,
-    aOptions,
-    aUseExternalSurfaceSize,
-    aSurfaceSize,
-    AllocateNamespace());
+  if (!session) {
+    session = InProcessCompositorSession::Create(
+      aWidget,
+      aLayerManager,
+      layerTreeId,
+      aScale,
+      aOptions,
+      aUseExternalSurfaceSize,
+      aSurfaceSize,
+      AllocateNamespace());
+  }
+
+#if defined(MOZ_WIDGET_ANDROID)
+  if (session) {
+    // Nothing to do if controller gets a nullptr
+    RefPtr<UiCompositorControllerChild> controller = CreateUiCompositorController(aWidget, session->RootLayerTreeId());
+    session->SetUiCompositorControllerChild(controller);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return session;
 }
 
 RefPtr<CompositorSession>
 GPUProcessManager::CreateRemoteSession(nsBaseWidget* aWidget,
                                        LayerManager* aLayerManager,
                                        const uint64_t& aRootLayerTreeId,
                                        CSSToLayoutDeviceScale aScale,
                                        const CompositorOptions& aOptions,
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -26,16 +26,17 @@ class MemoryReportingProcess;
 namespace layers {
 class IAPZCTreeManager;
 class CompositorOptions;
 class CompositorSession;
 class CompositorUpdateObserver;
 class PCompositorBridgeChild;
 class PImageBridgeChild;
 class RemoteCompositorSession;
+class UiCompositorControllerChild;
 } // namespace layers
 namespace widget {
 class CompositorWidget;
 } // namespace widget
 namespace dom {
 class ContentParent;
 class TabParent;
 class PVideoDecoderManagerChild;
@@ -61,16 +62,17 @@ class GPUProcessManager final : public G
   typedef layers::CompositorOptions CompositorOptions;
   typedef layers::CompositorSession CompositorSession;
   typedef layers::CompositorUpdateObserver CompositorUpdateObserver;
   typedef layers::IAPZCTreeManager IAPZCTreeManager;
   typedef layers::LayerManager LayerManager;
   typedef layers::PCompositorBridgeChild PCompositorBridgeChild;
   typedef layers::PImageBridgeChild PImageBridgeChild;
   typedef layers::RemoteCompositorSession RemoteCompositorSession;
+  typedef layers::UiCompositorControllerChild UiCompositorControllerChild;
 
 public:
   static void Initialize();
   static void Shutdown();
   static GPUProcessManager* Get();
 
   ~GPUProcessManager();
 
@@ -208,17 +210,20 @@ private:
 
   void HandleProcessLost();
 
   void EnsureVsyncIOThread();
   void ShutdownVsyncIOThread();
 
   void EnsureImageBridgeChild();
   void EnsureVRManager();
-  void EnsureUiCompositorController();
+
+#if defined(MOZ_WIDGET_ANDROID)
+  already_AddRefed<UiCompositorControllerChild> CreateUiCompositorController(nsBaseWidget* aWidget, const uint64_t aId);
+#endif // defined(MOZ_WIDGET_ANDROID)
 
   RefPtr<CompositorSession> CreateRemoteSession(
     nsBaseWidget* aWidget,
     LayerManager* aLayerManager,
     const uint64_t& aRootLayerTreeId,
     CSSToLayoutDeviceScale aScale,
     const CompositorOptions& aOptions,
     bool aUseExternalSurfaceSize,
--- a/gfx/ipc/InProcessCompositorSession.cpp
+++ b/gfx/ipc/InProcessCompositorSession.cpp
@@ -75,12 +75,18 @@ InProcessCompositorSession::Shutdown()
   // Destroy will synchronously wait for the parent to acknowledge shutdown,
   // at which point CBP will defer a Release on the compositor thread. We
   // can safely release our reference now, and let the destructor run on either
   // thread.
   mCompositorBridgeChild->Destroy();
   mCompositorBridgeChild = nullptr;
   mCompositorBridgeParent = nullptr;
   mCompositorWidget = nullptr;
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mUiCompositorControllerChild) {
+    mUiCompositorControllerChild->Destroy();
+    mUiCompositorControllerChild = nullptr;
+  }
+#endif //defined(MOZ_WIDGET_ANDROID)
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -53,17 +53,17 @@ parent:
   async Init(GfxPrefSetting[] prefs,
              GfxVarUpdate[] vars,
              DevicePrefs devicePrefs,
              LayerTreeIdMapping[] mapping);
 
   async InitVsyncBridge(Endpoint<PVsyncBridgeParent> endpoint);
   async InitImageBridge(Endpoint<PImageBridgeParent> endpoint);
   async InitVRManager(Endpoint<PVRManagerParent> endpoint);
-  async InitUiCompositorController(Endpoint<PUiCompositorControllerParent> endpoint);
+  async InitUiCompositorController(uint64_t rootLayerTreeId, Endpoint<PUiCompositorControllerParent> endpoint);
 
   // Called to update a gfx preference or variable.
   async UpdatePref(GfxPrefSetting pref);
   async UpdateVar(GfxVarUpdate var);
 
   // Create a new top-level compositor.
   async NewWidgetCompositor(Endpoint<PCompositorBridgeParent> endpoint,
                             CSSToLayoutDeviceScale scale,
--- a/gfx/ipc/RemoteCompositorSession.cpp
+++ b/gfx/ipc/RemoteCompositorSession.cpp
@@ -5,16 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RemoteCompositorSession.h"
 #include "mozilla/VsyncDispatcher.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/Unused.h"
 #include "nsBaseWidget.h"
+#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/layers/UiCompositorControllerChild.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
 
 namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 using namespace widget;
 
 RemoteCompositorSession::RemoteCompositorSession(nsBaseWidget* aWidget,
@@ -31,16 +34,19 @@ RemoteCompositorSession::RemoteComposito
     mAPZ->SetCompositorSession(this);
   }
 }
 
 RemoteCompositorSession::~RemoteCompositorSession()
 {
   // This should have been shutdown first.
   MOZ_ASSERT(!mCompositorBridgeChild);
+#if defined(MOZ_WIDGET_ANDROID)
+  MOZ_ASSERT(!mUiCompositorControllerChild);
+#endif //defined(MOZ_WIDGET_ANDROID)
 }
 
 void
 RemoteCompositorSession::NotifyDeviceReset(uint64_t aSeqNo)
 {
   MOZ_ASSERT(mWidget);
   mWidget->OnRenderingDeviceReset(aSeqNo);
 }
@@ -102,13 +108,19 @@ RemoteCompositorSession::Shutdown()
   mContentController = nullptr;
   if (mAPZ) {
     mAPZ->SetCompositorSession(nullptr);
   }
   mCompositorBridgeChild->Destroy();
   mCompositorBridgeChild = nullptr;
   mCompositorWidgetDelegate = nullptr;
   mWidget = nullptr;
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mUiCompositorControllerChild) {
+    mUiCompositorControllerChild->Destroy();
+    mUiCompositorControllerChild = nullptr;
+  }
+#endif //defined(MOZ_WIDGET_ANDROID)
   GPUProcessManager::Get()->UnregisterSession(this);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -156,17 +156,16 @@ public:
    * Notify content that the repaint requests have been flushed.
    */
   virtual void NotifyFlushComplete() = 0;
 
   virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) = 0;
 
   virtual void UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent) {}
   virtual void UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent) {}
-  virtual void SetScrollingRootContent(bool isRootContent) {}
 
   GeckoContentController() {}
 
   /**
    * Needs to be called on the main thread.
    */
   virtual void Destroy() {}
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -174,16 +174,19 @@ APZCTreeManager::APZCTreeManager()
       mApzcTreeLog("apzctree")
 {
   RefPtr<APZCTreeManager> self(this);
   NS_DispatchToMainThread(NS_NewRunnableFunction([self] {
     self->mFlushObserver = new CheckerboardFlushObserver(self);
   }));
   AsyncPanZoomController::InitializeGlobalState();
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
+#if defined(MOZ_WIDGET_ANDROID)
+  mToolbarAnimator = new AndroidDynamicToolbarAnimator();
+#endif // (MOZ_WIDGET_ANDROID)
 }
 
 APZCTreeManager::~APZCTreeManager()
 {
 }
 
 /*static*/ void
 APZCTreeManager::InitializeGlobalState()
@@ -716,16 +719,26 @@ APZCTreeManager::FlushApzRepaints(uint64
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
                                    ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId)
 {
   APZThreadUtils::AssertOnControllerThread();
 
+#if defined(MOZ_WIDGET_ANDROID)
+  MOZ_ASSERT(mToolbarAnimator);
+  nsEventStatus isConsumed = mToolbarAnimator->ReceiveInputEvent(aEvent);
+  // Check if the mToolbarAnimator consumed the event.
+  if (isConsumed == nsEventStatus_eConsumeNoDefault) {
+    APZCTM_LOG("Dynamic toolbar consumed event");
+    return isConsumed;
+  }
+#endif // (MOZ_WIDGET_ANDROID)
+
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
   // it if the input event goes into a block.
   if (aOutInputBlockId) {
     *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
   }
   nsEventStatus result = nsEventStatus_eIgnore;
   HitTestResult hitResult = HitNothing;
   switch (aEvent.mInputType) {
@@ -2117,10 +2130,25 @@ APZCTreeManager::CommonAncestor(AsyncPan
       break;
     }
     aApzc1 = aApzc1->GetParent();
     aApzc2 = aApzc2->GetParent();
   }
   return ancestor.forget();
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+void
+APZCTreeManager::InitializeDynamicToolbarAnimator(const int64_t& aRootLayerTreeId)
+{
+  MOZ_ASSERT(mToolbarAnimator);
+  mToolbarAnimator->Initialize(aRootLayerTreeId);
+}
+
+AndroidDynamicToolbarAnimator*
+APZCTreeManager::GetAndroidDynamicToolbarAnimator()
+{
+  return mToolbarAnimator;
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -14,16 +14,20 @@
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/layers/TouchCounter.h"// for TouchCounter
 #include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager
 #include "mozilla/Mutex.h"              // for Mutex
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "nsCOMPtr.h"                   // for already_AddRefed
 
+#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 
 namespace mozilla {
 class MultiTouchInput;
 
 namespace layers {
 
 class Layer;
 class AsyncPanZoomController;
@@ -520,14 +524,23 @@ private:
    * pref). */
   gfx::TreeLog mApzcTreeLog;
 
   class CheckerboardFlushObserver;
   friend class CheckerboardFlushObserver;
   RefPtr<CheckerboardFlushObserver> mFlushObserver;
 
   static float sDPI;
+
+#if defined(MOZ_WIDGET_ANDROID)
+public:
+  void InitializeDynamicToolbarAnimator(const int64_t& aRootLayerTreeId);
+  AndroidDynamicToolbarAnimator* GetAndroidDynamicToolbarAnimator();
+
+private:
+  RefPtr<AndroidDynamicToolbarAnimator> mToolbarAnimator;
+#endif // defined(MOZ_WIDGET_ANDROID)
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_PanZoomController_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -0,0 +1,882 @@
+/* -*- 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)
+  , mControllerDragChangedDirection(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) {
+        ScreenIntCoord direction = (delta > 0 ? MOVE_TOOLBAR_DOWN : MOVE_TOOLBAR_UP);
+        if (mControllerLastDragDirection && (direction != mControllerLastDragDirection)) {
+          mControllerDragChangedDirection = true;
+        }
+        mControllerLastDragDirection = direction;
+      }
+      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;
+  bool dragChangedDirection = mControllerDragChangedDirection;
+  mControllerDragChangedDirection = false;
+
+  // If the drag direction changed and the toolbar is partially visible, hide in the direction with the least distance to travel.
+  if (dragChangedDirection &&
+      (mControllerToolbarHeight != mControllerMaxToolbarHeight) &&
+      (mControllerToolbarHeight != 0)) {
+    direction = ((float)mControllerToolbarHeight / (float)mControllerMaxToolbarHeight) < 0.5f ? MOVE_TOOLBAR_UP : MOVE_TOOLBAR_DOWN;
+  }
+
+  // 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. Even though ShowToolbarIfNotVisible checks if
+  // snapshot toolbar is completely visible before showing, we don't want to enter this if block
+  // if the snapshot toolbar isn't completely visible to avoid early return.
+  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);
+    }
+  } else {
+    ShowToolbarIfNotVisible(aCurrentToolbarState);
+  }
+}
+
+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,212 @@
+/* -*- 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
+  bool    mControllerDragChangedDirection;     // Set to true if the drag ever goes in more than one direction
+  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/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -72,16 +72,17 @@
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "nsViewportInfo.h"             // for kViewportMinScale, kViewportMaxScale
 #include "prsystem.h"                   // for PR_GetPhysicalMemorySize
 #include "SharedMemoryBasic.h"          // for SharedMemoryBasic
 #include "ScrollSnap.h"                 // for ScrollSnapUtils
 #include "WheelScrollAnimation.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #include "AndroidAPZ.h"
+#include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 #define ENABLE_APZC_LOGGING 0
 // #define ENABLE_APZC_LOGGING 1
 
 #if ENABLE_APZC_LOGGING
 #  define APZC_LOG(...) printf_stderr("APZC: " __VA_ARGS__)
 #  define APZC_LOG_FM(fm, prefix, ...) \
@@ -1186,22 +1187,16 @@ nsEventStatus AsyncPanZoomController::On
       break;
   }
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
   APZC_LOG("%p got a touch-end in state %d\n", this, mState);
-
-  RefPtr<GeckoContentController> controller = GetGeckoContentController();
-  if (controller) {
-    controller->SetScrollingRootContent(false);
-  }
-
   OnTouchEndOrCancel();
 
   // In case no touch behavior triggered previously we can avoid sending
   // scroll events or requesting content repaint. This condition is added
   // to make tests consistent - in case touch-action is NONE (and therefore
   // no pans/zooms can be performed) we expected neither scroll or repaint
   // events.
   if (mState != NOTHING) {
@@ -2432,22 +2427,25 @@ bool AsyncPanZoomController::AttemptScro
 
     if (xChanged || yChanged) {
       ScheduleComposite();
     }
 
     if (!IsZero(adjustedDisplacement)) {
       ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom());
       if (CancelableBlockState* block = GetCurrentInputBlock()) {
-        if (block->AsTouchBlock() && (block->GetScrolledApzc() != this)) {
-          RefPtr<GeckoContentController> controller = GetGeckoContentController();
-          if (controller) {
-            controller->SetScrollingRootContent(IsRootContent());
+#if defined(MOZ_WIDGET_ANDROID)
+        if (block->AsTouchBlock() && (block->GetScrolledApzc() != this) && IsRootContent()) {
+          if (APZCTreeManager* manager = GetApzcTreeManager()) {
+            AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
+            MOZ_ASSERT(animator);
+            animator->SetScrollingRootContent();
           }
         }
+#endif
         block->SetScrolledApzc(this);
       }
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
 
     // Adjust the start point to reflect the consumed portion of the scroll.
     aStartPoint = aEndPoint + overscroll;
@@ -3449,16 +3447,31 @@ void AsyncPanZoomController::NotifyLayer
   }
 
   bool smoothScrollRequested = aLayerMetrics.GetDoSmoothScroll()
        && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
 
   // TODO if we're in a drag and scrollOffsetUpdated is set then we want to
   // ignore it
 
+#if defined(MOZ_WIDGET_ANDROID)
+  if (aLayerMetrics.IsRootContent()) {
+    if (APZCTreeManager* manager = GetApzcTreeManager()) {
+      AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
+      MOZ_ASSERT(animator);
+      CSSToScreenScale scale = ViewTargetAs<ScreenPixel>(aLayerMetrics.GetZoom().ToScaleFactor(),
+                                                         PixelCastJustification::ScreenIsParentLayerForRoot);
+      ScreenIntSize size = ScreenIntSize::Round(aLayerMetrics.GetRootCompositionSize() * scale);
+      if (animator->SetCompositionSize(size)) {
+        animator->UpdateRootFrameMetrics(aLayerMetrics);
+      }
+    }
+  }
+#endif
+
   if ((aIsFirstPaint && aThisLayerTreeUpdated) || isDefault) {
     // Initialize our internal state to something sane when the content
     // that was just painted is something we knew nothing about previously
     CancelAnimation();
 
     mScrollMetadata = aScrollMetadata;
     mExpectedGeckoMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
@@ -3821,16 +3834,27 @@ void AsyncPanZoomController::DispatchSta
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       // Let the compositor know about scroll state changes so it can manage
       // windowed plugins.
       if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
         mCompositorController->ScheduleHideAllPluginWindows();
       }
 #endif
     } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) {
+#if defined(MOZ_WIDGET_ANDROID)
+      // The Android UI thread only shows overlay UI elements when the content is not being
+      // panned or zoomed and it is in a steady state. So the FrameMetrics only need to be
+      // updated when the transform ends.
+      if (APZCTreeManager* manager = GetApzcTreeManager()) {
+        AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
+        MOZ_ASSERT(animator);
+        animator->UpdateRootFrameMetrics(mFrameMetrics);
+      }
+#endif
+
       controller->NotifyAPZStateChange(
           GetGuid(), APZStateChange::eTransformEnd);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
         mCompositorController->ScheduleShowAllPluginWindows();
       }
 #endif
     }
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -864,22 +864,16 @@ ClientLayerManager::GetBackendName(nsASt
 
 bool
 ClientLayerManager::AsyncPanZoomEnabled() const
 {
   return mWidget && mWidget->AsyncPanZoomEnabled();
 }
 
 void
-ClientLayerManager::SetNextPaintSyncId(int32_t aSyncId)
-{
-  mForwarder->SetPaintSyncId(aSyncId);
-}
-
-void
 ClientLayerManager::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch)
 {
   mForwarder->SetLayerObserverEpoch(aLayerObserverEpoch);
 }
 
 void
 ClientLayerManager::AddDidCompositeObserver(DidCompositeObserver* aObserver)
 {
--- a/gfx/layers/client/ClientLayerManager.h
+++ b/gfx/layers/client/ClientLayerManager.h
@@ -233,18 +233,16 @@ public:
   virtual void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override;
 
   virtual uint64_t GetLastTransactionId() override { return mLatestTransactionId; }
 
   float RequestProperty(const nsAString& aProperty) override;
 
   bool AsyncPanZoomEnabled() const override;
 
-  void SetNextPaintSyncId(int32_t aSyncId);
-
   virtual void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch) override;
 
   virtual void AddDidCompositeObserver(DidCompositeObserver* aObserver) override;
   virtual void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override;
 
   virtual already_AddRefed<PersistentBufferProvider>
   CreatePersistentBufferProvider(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override;
 
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -34,16 +34,17 @@
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl, etc
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
 #include "UnitTransforms.h"             // for TransformTo
 #include "gfxPrefs.h"
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
+# include "mozilla/layers/UiCompositorControllerParent.h"
 # include "mozilla/widget/AndroidCompositorWidget.h"
 #endif
 #include "GeckoProfiler.h"
 #include "FrameUniformityData.h"
 #include "TreeTraversal.h"              // for ForEachNode, BreadthFirstSearch
 #include "VsyncSource.h"
 
 struct nsCSSValueSharedList;
@@ -67,17 +68,16 @@ ContentMightReflowOnOrientationChange(co
   return rect.width != rect.height;
 }
 
   AsyncCompositionManager::AsyncCompositionManager(CompositorBridgeParent* aParent,
                                                    HostLayerManager* aManager)
   : mLayerManager(aManager)
   , mIsFirstPaint(true)
   , mLayersUpdated(false)
-  , mPaintSyncId(0)
   , mReadyForCompose(true)
   , mCompositorBridge(aParent)
 {
 }
 
 AsyncCompositionManager::~AsyncCompositionManager()
 {
 }
@@ -849,16 +849,17 @@ AsyncCompositionManager::ApplyAsyncConte
       {
         Maybe<ParentLayerIntRect> clipDeferredFromChildren = stackDeferredClips.top();
         stackDeferredClips.pop();
         LayerToParentLayerMatrix4x4 oldTransform = layer->GetTransformTyped() *
             AsyncTransformMatrix();
 
         AsyncTransformComponentMatrix combinedAsyncTransform;
         bool hasAsyncTransform = false;
+        // Only set on the root layer for Android.
         ScreenMargin fixedLayerMargins;
 
         // Each layer has multiple clips:
         //  - Its local clip, which is fixed to the layer contents, i.e. it moves
         //    with those async transforms which the layer contents move with.
         //  - Its scrolled clip, which moves with all async transforms.
         //  - For each ScrollMetadata on the layer, a scroll clip. This includes
         //    the composition bounds and any other clips induced by layout. This
@@ -938,40 +939,35 @@ AsyncCompositionManager::ApplyAsyncConte
           // should not need the root content metrics at all. See bug 1201529 comment
           // 6 for details.
           if (!(*aOutFoundRoot)) {
             *aOutFoundRoot = metrics.IsRootContent() ||       /* RCD */
                   (layer->GetParent() == nullptr &&          /* rootmost metrics */
                    i + 1 >= layer->GetScrollMetadataCount());
             if (*aOutFoundRoot) {
               mRootScrollableId = metrics.GetScrollId();
-              CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor();
+              Compositor* compositor = mLayerManager->GetCompositor();
               if (mIsFirstPaint) {
-                LayerIntPoint scrollOffsetLayerPixels = RoundedToInt(metrics.GetScrollOffset() * geckoZoom);
-                mContentRect = metrics.GetScrollableRect();
-                SetFirstPaintViewport(scrollOffsetLayerPixels,
-                                      geckoZoom,
-                                      mContentRect);
-              } else {
-                ParentLayerPoint scrollOffset = controller->GetCurrentAsyncScrollOffset(
-                    AsyncPanZoomController::RESPECT_FORCE_DISABLE);
-                // Compute the painted displayport in document-relative CSS pixels.
-                CSSRect displayPort(metrics.GetCriticalDisplayPort().IsEmpty() ?
-                    metrics.GetDisplayPort() :
-                    metrics.GetCriticalDisplayPort());
-                displayPort += metrics.GetScrollOffset();
-                SyncFrameMetrics(scrollOffset,
-                    geckoZoom * asyncTransformWithoutOverscroll.mScale,
-                    metrics.GetScrollableRect(), displayPort, geckoZoom, mLayersUpdated,
-                    mPaintSyncId, fixedLayerMargins);
-                mFixedLayerMargins = fixedLayerMargins;
+                if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
+                  AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator();
+                  MOZ_ASSERT(animator);
+                  animator->UpdateRootFrameMetrics(metrics);
+                  animator->FirstPaint();
+                }
+              }
+              if (mLayersUpdated) {
+                if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
+                  AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator();
+                  MOZ_ASSERT(animator);
+                  animator->NotifyLayersUpdated();
+                }
                 mLayersUpdated = false;
-                mPaintSyncId = 0;
               }
               mIsFirstPaint = false;
+              fixedLayerMargins = mFixedLayerMargins;
             }
           }
 #else
           *aOutFoundRoot = false;
           // Non-Android platforms still care about this flag being cleared after
           // the first call to TransformShadowTree().
           mIsFirstPaint = false;
 #endif
@@ -1370,16 +1366,34 @@ AsyncCompositionManager::TransformShadow
     animationProcess, layerAreaAnimated, aVsyncRate);
 
   if (!wantNextFrame) {
     // Clean up the CompositorAnimationStorage because
     // there are no active animations running
     storage->Clear();
   }
 
+  // Advance animations to the next expected vsync timestamp, if we can
+  // get it.
+  TimeStamp nextFrame = aCurrentFrame;
+
+  MOZ_ASSERT(aVsyncRate != TimeDuration::Forever());
+  if (aVsyncRate != TimeDuration::Forever()) {
+    nextFrame += aVsyncRate;
+  }
+
+#if defined(MOZ_WIDGET_ANDROID)
+  Compositor* compositor = mLayerManager->GetCompositor();
+  if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
+    AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator();
+    MOZ_ASSERT(animator);
+    wantNextFrame |= animator->UpdateAnimation(nextFrame);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
   // Reset the previous time stamp if we don't already have any running
   // animations to avoid using the time which is far behind for newly
   // started animations.
   mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame : TimeStamp();
 
   if (!(aSkip & TransformsToSkip::APZ)) {
     // FIXME/bug 775437: unify this interface with the ~native-fennec
     // derived code
@@ -1397,25 +1411,16 @@ AsyncCompositionManager::TransformShadow
 #if defined(MOZ_WIDGET_ANDROID)
       MOZ_ASSERT(foundRoot);
       if (foundRoot && mFixedLayerMargins != ScreenMargin()) {
         MoveScrollbarForLayerMargin(root, mRootScrollableId, mFixedLayerMargins);
       }
 #endif
     }
 
-    // Advance APZ animations to the next expected vsync timestamp, if we can
-    // get it.
-    TimeStamp nextFrame = aCurrentFrame;
-
-    MOZ_ASSERT(aVsyncRate != TimeDuration::Forever());
-    if (aVsyncRate != TimeDuration::Forever()) {
-      nextFrame += aVsyncRate;
-    }
-
     bool apzAnimating = SampleAPZAnimations(LayerMetricsWrapper(root), nextFrame);
     mAnimationMetricsTracker.UpdateApzAnimationInProgress(apzAnimating, aVsyncRate);
     wantNextFrame |= apzAnimating;
   }
 
   HostLayer* rootComposite = root->AsHostLayer();
 
   gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform();
@@ -1424,47 +1429,18 @@ AsyncCompositionManager::TransformShadow
 
   if (gfxPrefs::CollectScrollTransforms()) {
     RecordShadowTransforms(root);
   }
 
   return wantNextFrame;
 }
 
-void
-AsyncCompositionManager::SetFirstPaintViewport(const LayerIntPoint& aOffset,
-                                               const CSSToLayerScale& aZoom,
-                                               const CSSRect& aCssPageRect)
-{
-#ifdef MOZ_WIDGET_ANDROID
-  widget::AndroidCompositorWidget* widget =
-      mLayerManager->GetCompositor()->GetWidget()->AsAndroid();
-  if (!widget) {
-    return;
-  }
-  widget->SetFirstPaintViewport(aOffset, aZoom, aCssPageRect);
-#endif
-}
-
+#if defined(MOZ_WIDGET_ANDROID)
 void
-AsyncCompositionManager::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
-                                          const CSSToParentLayerScale& aZoom,
-                                          const CSSRect& aCssPageRect,
-                                          const CSSRect& aDisplayPort,
-                                          const CSSToLayerScale& aPaintedResolution,
-                                          bool aLayersUpdated,
-                                          int32_t aPaintSyncId,
-                                          ScreenMargin& aFixedLayerMargins)
+AsyncCompositionManager::SetFixedLayerMarginsBottom(ScreenIntCoord aBottom)
 {
-#ifdef MOZ_WIDGET_ANDROID
-  widget::AndroidCompositorWidget* widget =
-      mLayerManager->GetCompositor()->GetWidget()->AsAndroid();
-  if (!widget) {
-    return;
-  }
-  widget->SyncFrameMetrics(
-      aScrollOffset, aZoom, aCssPageRect, aDisplayPort, aPaintedResolution,
-      aLayersUpdated, aPaintSyncId, aFixedLayerMargins);
-#endif
+  mFixedLayerMargins.bottom = aBottom;
 }
+#endif // defined(MOZ_WIDGET_ANDROID)
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -73,41 +73,37 @@ public:
 
   explicit AsyncCompositionManager(CompositorBridgeParent* aParent, HostLayerManager* aManager);
 
   /**
    * This forces the is-first-paint flag to true. This is intended to
    * be called by the widget code when it loses its viewport information
    * (or for whatever reason wants to refresh the viewport information).
    * The information refresh happens because the compositor will call
-   * SetFirstPaintViewport on the next frame of composition.
+   * AndroidDynamicToolbarAnimator::FirstPaint() on the next frame of composition.
    */
   void ForceIsFirstPaint() { mIsFirstPaint = true; }
 
   // Sample transforms for layer trees.  Return true to request
   // another animation frame.
   enum class TransformsToSkip : uint8_t { NoneOfThem = 0, APZ = 1 };
   bool TransformShadowTree(TimeStamp aCurrentFrame,
                            TimeDuration aVsyncRate,
                            TransformsToSkip aSkip = TransformsToSkip::NoneOfThem);
 
   // Calculates the correct rotation and applies the transform to
   // our layer manager
   void ComputeRotation();
 
   // Call after updating our layer tree.
-  void Updated(bool isFirstPaint, const TargetConfig& aTargetConfig,
-               int32_t aPaintSyncId)
+  void Updated(bool isFirstPaint, const TargetConfig& aTargetConfig)
   {
     mIsFirstPaint |= isFirstPaint;
     mLayersUpdated = true;
     mTargetConfig = aTargetConfig;
-    if (aPaintSyncId) {
-      mPaintSyncId = aPaintSyncId;
-    }
   }
 
   bool RequiresReorientation(mozilla::dom::ScreenOrientationInternal aOrientation) const
   {
     return mTargetConfig.orientation() != aOrientation;
   }
 
   // True if the underlying layer tree is ready to be composited.
@@ -144,28 +140,16 @@ private:
   bool ApplyAsyncContentTransformToTree(Layer* aLayer,
                                         bool* aOutFoundRoot);
   /**
    * Update the shadow transform for aLayer assuming that is a scrollbar,
    * so that it stays in sync with the content that is being scrolled by APZ.
    */
   void ApplyAsyncTransformToScrollbar(Layer* aLayer);
 
-  void SetFirstPaintViewport(const LayerIntPoint& aOffset,
-                             const CSSToLayerScale& aZoom,
-                             const CSSRect& aCssPageRect);
-  void SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
-                        const CSSToParentLayerScale& aZoom,
-                        const CSSRect& aCssPageRect,
-                        const CSSRect& aDisplayPort,
-                        const CSSToLayerScale& aPaintedResolution,
-                        bool aLayersUpdated,
-                        int32_t aPaintSyncId,
-                        ScreenMargin& aFixedLayerMargins);
-
   /**
    * Adds a translation to the transform of any fixed position (whose parent
    * layer is not fixed) or sticky position layer descendant of
    * |aTransformedSubtreeRoot|. The translation is chosen so that the layer's
    * anchor point relative to |aTransformedSubtreeRoot|'s parent layer is the same
    * as it was when |aTransformedSubtreeRoot|'s GetLocalTransform() was
    * |aPreviousTransformForRoot|. |aCurrentTransformForRoot| is
    * |aTransformedSubtreeRoot|'s current GetLocalTransform() modulo any
@@ -226,29 +210,30 @@ private:
   // front-end (e.g. Java on Android) about this so that it take the new page
   // size and zoom into account when providing us with the next view transform.
   bool mIsFirstPaint;
 
   // This flag is set during a layers update, so that the first composition
   // after a layers update has it set. It is cleared after that first composition.
   bool mLayersUpdated;
 
-  int32_t mPaintSyncId;
-
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
   LayerTransformRecorder mLayerTransformRecorder;
 
   TimeStamp mPreviousFrameTimeStamp;
   AnimationMetricsTracker mAnimationMetricsTracker;
 
   CompositorBridgeParent* mCompositorBridge;
 
 #ifdef MOZ_WIDGET_ANDROID
+public:
+  void SetFixedLayerMarginsBottom(ScreenIntCoord aBottom);
+private:
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   FrameMetrics::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
 };
 
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -48,24 +48,25 @@
 #include "nsAppRunner.h"
 #include "mozilla/RefPtr.h"                   // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_WARNING, NS_RUNTIMEABORT, etc
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion, etc
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID)
 #include <android/log.h>
 #include <android/native_window.h>
-#endif
-#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/widget/AndroidCompositorWidget.h"
 #include "opengl/CompositorOGL.h"
+#include "GLConsts.h"
 #include "GLContextEGL.h"
 #include "GLContextProvider.h"
+#include "mozilla/Unused.h"
 #include "mozilla/widget/AndroidCompositorWidget.h"
 #include "ScopedGLHelpers.h"
 #endif
 #include "GeckoProfiler.h"
 #include "TextRenderer.h"               // for TextRenderer
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "TreeTraversal.h"              // for ForEachNode
 
@@ -142,16 +143,19 @@ HostLayerManager::RecordUpdateTime(float
  */
 LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor)
 : mUnusedApzTransformWarning(false)
 , mDisabledApzWarning(false)
 , mCompositor(aCompositor)
 , mInTransaction(false)
 , mIsCompositorReady(false)
 , mGeometryChanged(true)
+#if defined(MOZ_WIDGET_ANDROID)
+, mScreenPixelsTarget(nullptr)
+#endif // defined(MOZ_WIDGET_ANDROID)
 {
   mTextRenderer = new TextRenderer();
   mDiagnostics = MakeUnique<Diagnostics>();
   MOZ_ASSERT(aCompositor);
 
 #ifdef USE_SKIA
   mPaintCounter = nullptr;
 #endif
@@ -833,16 +837,35 @@ ClearLayerFlags(Layer* aLayer) {
       [] (Layer* layer)
       {
         if (layer->AsHostLayer()) {
           static_cast<LayerComposite*>(layer->AsHostLayer())->SetLayerComposited(false);
         }
       });
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+class ScopedCompositorRenderOffset {
+public:
+  ScopedCompositorRenderOffset(CompositorOGL* aCompositor, const ScreenPoint& aOffset) :
+    mCompositor(aCompositor),
+    mOriginalOffset(mCompositor->GetScreenRenderOffset())
+  {
+    mCompositor->SetScreenRenderOffset(aOffset);
+  }
+  ~ScopedCompositorRenderOffset()
+  {
+    mCompositor->SetScreenRenderOffset(mOriginalOffset);
+  }
+private:
+  CompositorOGL* const mCompositor;
+  const ScreenPoint mOriginalOffset;
+};
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 void
 LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion)
 {
   PROFILER_LABEL("LayerManagerComposite", "Render",
     js::ProfileEntry::Category::GRAPHICS);
 
   if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) {
     NS_WARNING("Call on destroyed layer manager");
@@ -918,16 +941,21 @@ LayerManagerComposite::Render(const nsIn
     clipRect = *mRoot->GetClipRect();
     IntRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
     mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, aOpaqueRegion, nullptr, &actualBounds);
   } else {
     gfx::IntRect rect;
     mCompositor->BeginFrame(aInvalidRegion, nullptr, bounds, aOpaqueRegion, &rect, &actualBounds);
     clipRect = ParentLayerIntRect(rect.x, rect.y, rect.width, rect.height);
   }
+#if defined(MOZ_WIDGET_ANDROID)
+  int32_t toolbarHeight = RenderToolbar();
+  // This doesn't affect the projection matrix after BeginFrame has been called.
+  ScopedCompositorRenderOffset scopedOffset(mCompositor->AsCompositorOGL(), ScreenPoint(0, toolbarHeight));
+#endif
 
   if (actualBounds.IsEmpty()) {
     mCompositor->GetWidget()->PostRender(&widgetContext);
     return;
   }
 
   // Allow widget to render a custom background.
   mCompositor->GetWidget()->DrawWindowUnderlay(
@@ -972,16 +1000,20 @@ LayerManagerComposite::Render(const nsIn
   }
 
   // Allow widget to render a custom foreground.
   mCompositor->GetWidget()->DrawWindowOverlay(
     &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds));
 
   mCompositor->NormalDrawingDone();
 
+#if defined(MOZ_WIDGET_ANDROID)
+  HandlePixelsTarget();
+#endif // defined(MOZ_WIDGET_ANDROID)
+
   // Debugging
   RenderDebugOverlay(actualBounds);
 
   {
     PROFILER_LABEL("LayerManagerComposite", "EndFrame",
       js::ProfileEntry::Category::GRAPHICS);
 
     mCompositor->EndFrame();
@@ -1026,33 +1058,16 @@ public:
   {
     mCompositor->SetDestinationSurfaceSize(mOriginalSize);
   }
 private:
   CompositorOGL* const mCompositor;
   const gfx::IntSize mOriginalSize;
 };
 
-class ScopedCompositorRenderOffset {
-public:
-  ScopedCompositorRenderOffset(CompositorOGL* aCompositor, const ScreenPoint& aOffset) :
-    mCompositor(aCompositor),
-    mOriginalOffset(mCompositor->GetScreenRenderOffset())
-  {
-    mCompositor->SetScreenRenderOffset(aOffset);
-  }
-  ~ScopedCompositorRenderOffset()
-  {
-    mCompositor->SetScreenRenderOffset(mOriginalOffset);
-  }
-private:
-  CompositorOGL* const mCompositor;
-  const ScreenPoint mOriginalOffset;
-};
-
 class ScopedContextSurfaceOverride {
 public:
   ScopedContextSurfaceOverride(GLContextEGL* aContext, void* aSurface) :
     mContext(aContext)
   {
     MOZ_ASSERT(aSurface);
     mContext->SetEGLSurfaceOverride(aSurface);
     mContext->MakeCurrent(true);
@@ -1160,16 +1175,75 @@ LayerManagerComposite::RenderToPresentat
 
   const IntRect clipRect = IntRect::Truncate(0, 0, actualWidth, actualHeight);
 
   RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect));
   RootLayer()->RenderLayer(clipRect, Nothing());
 
   mCompositor->EndFrame();
 }
+
+int32_t
+LayerManagerComposite::RenderToolbar()
+{
+  int32_t toolbarHeight = 0;
+
+  // If GetTargetContext returns null we are drawing to the screen so draw the toolbar offset if present.
+  if (mCompositor->GetTargetContext() != nullptr) {
+    return toolbarHeight;
+  }
+
+  if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) {
+    AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator();
+    MOZ_ASSERT(animator);
+    toolbarHeight = animator->GetCurrentToolbarHeight();
+    if (toolbarHeight == 0) {
+      return toolbarHeight;
+    }
+
+    EffectChain effects;
+    effects.mPrimaryEffect = animator->GetToolbarEffect(mCompositor->AsCompositorOGL());
+    if (!effects.mPrimaryEffect) {
+      // No toolbar texture so just draw a red square
+      effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(1, 0, 0));
+    }
+    mCompositor->DrawQuad(gfx::Rect(0, 0, mRenderBounds.width, toolbarHeight),
+                          IntRect(0, 0, mRenderBounds.width, toolbarHeight), effects, 1.0, gfx::Matrix4x4());
+
+    // Move the content down the surface by the toolbar's height so they don't overlap
+    gfx::Matrix4x4 mat = mCompositor->AsCompositorOGL()->GetProjMatrix();
+    mat.PreTranslate(0.0f, float(toolbarHeight), 0.0f);
+    mCompositor->AsCompositorOGL()->SetProjMatrix(mat);
+  }
+
+  return toolbarHeight;
+}
+
+// Used by robocop tests to get a snapshot of the frame buffer.
+void
+LayerManagerComposite::HandlePixelsTarget()
+{
+  if (!mScreenPixelsTarget) {
+    return;
+  }
+
+  int32_t bufferWidth = mRenderBounds.width;
+  int32_t bufferHeight = mRenderBounds.height;
+  ipc::Shmem mem;
+  if (!mScreenPixelsTarget->AllocPixelBuffer(bufferWidth * bufferHeight * sizeof(uint32_t), &mem)) {
+    // Failed to alloc shmem, Just bail out.
+    return;
+  }
+  CompositorOGL* compositor = mCompositor->AsCompositorOGL();
+  GLContext* gl = compositor->gl();
+  MOZ_ASSERT(gl);
+  gl->fReadPixels(0, 0, bufferWidth, bufferHeight, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, mem.get<uint8_t>());
+  Unused << mScreenPixelsTarget->SendScreenPixels(mem, ScreenIntSize(bufferWidth, bufferHeight));
+  mScreenPixelsTarget = nullptr;
+}
 #endif
 
 class TextLayerComposite : public TextLayer,
                            public LayerComposite
 {
 public:
   explicit TextLayerComposite(LayerManagerComposite *aManager)
     : TextLayer(aManager, nullptr)
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -58,16 +58,17 @@ class ImageLayer;
 class ImageLayerComposite;
 class LayerComposite;
 class RefLayerComposite;
 class PaintedLayerComposite;
 class TextRenderer;
 class CompositingRenderTarget;
 struct FPSState;
 class PaintCounter;
+class UiCompositorControllerParent;
 
 static const int kVisualWarningDuration = 150; // ms
 
 // An implementation of LayerManager that acts as a pair with ClientLayerManager
 // and is mirrored across IPDL. This gets managed/updated by LayerTransactionParent.
 class HostLayerManager : public LayerManager
 {
 public:
@@ -208,16 +209,23 @@ protected:
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
   TimeStamp mCompositeUntilTime;
+#if defined(MOZ_WIDGET_ANDROID)
+public:
+  // Used by UiCompositorControllerParent to set itself as the target for the
+  // contents of the frame buffer after a composite.
+  // Implemented in LayerManagerComposite
+  virtual void RequestScreenPixels(UiCompositorControllerParent* aController) {}
+#endif // defined(MOZ_WIDGET_ANDROID)
 };
 
 // A layer manager implementation that uses the Compositor API
 // to render layers.
 class LayerManagerComposite final : public HostLayerManager
 {
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::IntSize IntSize;
@@ -424,16 +432,20 @@ private:
   void UpdateAndRender();
 
   /**
    * Render the current layer tree to the active target.
    */
   void Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion);
 #if defined(MOZ_WIDGET_ANDROID)
   void RenderToPresentationSurface();
+  // Returns the height of the toolbar in screen pixels.
+  int32_t RenderToolbar();
+  // Used by robocop tests to get a snapshot of the frame buffer.
+  void HandlePixelsTarget();
 #endif
 
   /**
    * We need to know our invalid region before we're ready to render.
    */
   void InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const gfx::IntRect& aBounds);
 
   /**
@@ -477,16 +489,25 @@ private:
 
 #ifdef USE_SKIA
   /**
    * Render paint and composite times above the frame.
    */
   void DrawPaintTimes(Compositor* aCompositor);
   RefPtr<PaintCounter> mPaintCounter;
 #endif
+#if defined(MOZ_WIDGET_ANDROID)
+public:
+  virtual void RequestScreenPixels(UiCompositorControllerParent* aController)
+  {
+    mScreenPixelsTarget = aController;
+  }
+private:
+  UiCompositorControllerParent* mScreenPixelsTarget;
+#endif // defined(MOZ_WIDGET_ANDROID)
 };
 
 /**
  * Compositor layers are for use with OMTC on the compositor thread only. There
  * must be corresponding Client layers on the content thread. For composite
  * layers, the layer manager only maintains the layer tree.
  */
 class HostLayer
--- a/gfx/layers/ipc/APZChild.cpp
+++ b/gfx/layers/ipc/APZChild.cpp
@@ -48,23 +48,16 @@ APZChild::RecvUpdateOverscrollVelocity(c
 mozilla::ipc::IPCResult
 APZChild::RecvUpdateOverscrollOffset(const float& aX, const float& aY, const bool& aIsRootContent)
 {
   mController->UpdateOverscrollOffset(aX, aY, aIsRootContent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-APZChild::RecvSetScrollingRootContent(const bool& aIsRootContent)
-{
-  mController->SetScrollingRootContent(aIsRootContent);
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 APZChild::RecvNotifyMozMouseScrollEvent(const ViewID& aScrollId,
                                         const nsString& aEvent)
 {
   mController->NotifyMozMouseScrollEvent(aScrollId, aEvent);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -26,18 +26,16 @@ public:
   ~APZChild();
 
   mozilla::ipc::IPCResult RecvRequestContentRepaint(const FrameMetrics& frame) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollVelocity(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollOffset(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
-  mozilla::ipc::IPCResult RecvSetScrollingRootContent(const bool& aIsRootContent) override;
-
   mozilla::ipc::IPCResult RecvNotifyMozMouseScrollEvent(const ViewID& aScrollId,
                                                         const nsString& aEvent) override;
 
   mozilla::ipc::IPCResult RecvNotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                                    const APZStateChange& aChange,
                                                    const int& aArg) override;
 
   mozilla::ipc::IPCResult RecvNotifyFlushComplete() override;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1192,17 +1192,17 @@ CompositorBridgeParent::ShadowLayersUpda
   // change, dimension change would be done at the stage, update the size here is free of
   // race condition.
   mLayerManager->UpdateRenderBounds(targetConfig.naturalBounds());
   mLayerManager->SetRegionToClear(targetConfig.clearRegion());
   if (mLayerManager->GetCompositor()) {
     mLayerManager->GetCompositor()->SetScreenRotation(targetConfig.rotation());
   }
 
-  mCompositionManager->Updated(aInfo.isFirstPaint(), targetConfig, aInfo.paintSyncId());
+  mCompositionManager->Updated(aInfo.isFirstPaint(), targetConfig);
   Layer* root = aLayerTree->GetRoot();
   mLayerManager->SetRoot(root);
 
   if (mApzcTreeManager && !aInfo.isRepeatTransaction() && aHitTestUpdate) {
     AutoResolveRefLayers resolve(mCompositionManager);
 
     mApzcTreeManager->UpdateHitTestingTree(
       mRootLayerTreeID, root, aInfo.isFirstPaint(),
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -35,16 +35,17 @@
 #include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/layers/MetricsSharingController.h"
 #include "mozilla/layers/PCompositorBridgeParent.h"
 #include "mozilla/layers/APZTestData.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "nsISupportsImpl.h"
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
 
 class MessageLoop;
 class nsIWidget;
 
 namespace mozilla {
 
 class CancelableRunnable;
 
@@ -379,16 +380,17 @@ public:
     // this is needed in case a device reset occurs in between allocating a
     // RefLayer id on the parent, and allocating a PLayerTransaction on the
     // child.
     Maybe<uint64_t> mPendingCompositorUpdate;
 
     CompositorController* GetCompositorController() const;
     MetricsSharingController* CrossProcessSharingController() const;
     MetricsSharingController* InProcessSharingController() const;
+    RefPtr<UiCompositorControllerParent> mUiControllerParent;
   };
 
   /**
    * Lookup the indirect shadow tree for |aId| and return it if it
    * exists.  Otherwise null is returned.  This must only be called on
    * the compositor thread.
    */
   static LayerTreeState* GetIndirectShadowTree(uint64_t aId);
@@ -461,16 +463,27 @@ public:
   PWebRenderBridgeParent* AllocPWebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                                       const LayoutDeviceIntSize& aSize,
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                       uint32_t* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
   static void SetWebRenderProfilerEnabled(bool aEnabled);
 
   static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId(const uint64_t& aLayersId);
+
+#if defined(MOZ_WIDGET_ANDROID)
+  gfx::IntSize GetEGLSurfaceSize() {
+    return mEGLSurfaceSize;
+  }
+
+  uint64_t GetRootLayerTreeId() {
+    return mRootLayerTreeID;
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
 private:
 
   void Initialize();
 
   /**
    * Called during destruction in order to release resources as early as possible.
    */
   void StopAndClearResources();
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -553,13 +553,12 @@ struct TransactionInfo
   uint64_t id;
   TargetConfig targetConfig;
   PluginWindowData[] plugins;
   bool isFirstPaint;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
   TimeStamp transactionStart;
-  int32_t paintSyncId;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -51,18 +51,16 @@ parent:
 child:
 
   async RequestContentRepaint(FrameMetrics frame);
 
   async UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent);
 
   async UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent);
 
-  async SetScrollingRootContent(bool aIsRootContent);
-
   async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent);
 
   async NotifyAPZStateChange(ScrollableLayerGuid aGuid, APZStateChange aChange, int aArg);
 
   async NotifyFlushComplete();
 
   async NotifyAsyncScrollbarDragRejected(ViewID aScrollId);
 
--- a/gfx/layers/ipc/PUiCompositorController.ipdl
+++ b/gfx/layers/ipc/PUiCompositorController.ipdl
@@ -1,29 +1,46 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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/. */
 
+using CSSRect from "Units.h";
+using CSSToScreenScale from "Units.h";
+using ScreenIntSize from "Units.h";
+using ScreenPoint from "Units.h";
+
 namespace mozilla {
 namespace layers {
 
 /**
  * The PUiCompositorController protocol is used to pause and resume the
  * compositor from the UI thread. Primarily used on Android to coordinate registering and
  * releasing the surface with the compositor.
  */
 sync protocol PUiCompositorController
 {
 
 parent:
   // Pause/resume the compositor. These are intended to be used on mobile, when
   // the compositor needs to pause/resume in lockstep with the application.
-  sync Pause(uint64_t layersId);
-  sync Resume(uint64_t layersId);
-  sync ResumeAndResize(uint64_t layersId, int32_t height, int32_t width);
-  async InvalidateAndRender(uint64_t layersId);
+  sync Pause();
+  sync Resume();
+  sync ResumeAndResize(int32_t aWidth, int32_t aHeight);
+
+  async InvalidateAndRender();
+  async MaxToolbarHeight(int32_t aHeight);
+  async Pinned(bool aPinned, int32_t aReason);
+  async ToolbarAnimatorMessageFromUI(int32_t aMessage);
+  async DefaultClearColor(uint32_t aColor);
+  async RequestScreenPixels();
+  async EnableLayerUpdateNotifications(bool aEnable);
+  async ToolbarPixelsToCompositor(Shmem aMem, ScreenIntSize aSize);
+child:
+  async ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
+  async RootFrameMetrics(ScreenPoint aScrollOffset, CSSToScreenScale aZoom, CSSRect aPage);
+  async ScreenPixels(Shmem aMem, ScreenIntSize aSize);
 };
 
 } // layers
 } // mozilla
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -198,30 +198,16 @@ RemoteContentController::UpdateOverscrol
     return;
   }
   if (mCanSend) {
     Unused << SendUpdateOverscrollOffset(aX, aY, aIsRootContent);
   }
 }
 
 void
-RemoteContentController::SetScrollingRootContent(bool aIsRootContent)
-{
-  if (MessageLoop::current() != mCompositorThread) {
-    mCompositorThread->PostTask(NewRunnableMethod<bool>(this,
-                                             &RemoteContentController::SetScrollingRootContent,
-                                             aIsRootContent));
-    return;
-  }
-  if (mCanSend) {
-    Unused << SendSetScrollingRootContent(aIsRootContent);
-  }
-}
-
-void
 RemoteContentController::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                                    const nsString& aEvent)
 {
   if (MessageLoop::current() != mCompositorThread) {
     // We have to send messages from the compositor thread
     mCompositorThread->PostTask(NewRunnableMethod<FrameMetrics::ViewID,
                                         nsString>(this,
                                                   &RemoteContentController::NotifyMozMouseScrollEvent,
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -62,18 +62,16 @@ public:
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg) override;
 
   virtual void UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent) override;
 
   virtual void UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent) override;
 
-  virtual void SetScrollingRootContent(bool aIsRootContent) override;
-
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
 
   virtual void NotifyFlushComplete() override;
 
   virtual void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId) override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -186,17 +186,16 @@ KnowsCompositor::~KnowsCompositor()
 {}
 
 ShadowLayerForwarder::ShadowLayerForwarder(ClientLayerManager* aClientLayerManager)
  : mClientLayerManager(aClientLayerManager)
  , mMessageLoop(MessageLoop::current())
  , mDiagnosticTypes(DiagnosticTypes::NO_DIAGNOSTIC)
  , mIsFirstPaint(false)
  , mWindowOverlayChanged(false)
- , mPaintSyncId(0)
  , mNextLayerHandle(1)
 {
   mTxn = new Transaction();
   if (TabGroup* tabGroup = mClientLayerManager->GetTabGroup()) {
     mEventTarget = tabGroup->EventTargetFor(TaskCategory::Other);
   }
   MOZ_ASSERT(mEventTarget || !XRE_IsContentProcess());
   mActiveResourceTracker = MakeUnique<ActiveResourceTracker>(
@@ -731,17 +730,16 @@ ShadowLayerForwarder::EndTransaction(con
   info.fwdTransactionId() = GetFwdTransactionId();
   info.id() = aId;
   info.plugins() = mPluginWindowData;
   info.isFirstPaint() = mIsFirstPaint;
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
   info.transactionStart() = aTransactionStart;
-  info.paintSyncId() = mPaintSyncId;
 
   TargetConfig targetConfig(mTxn->mTargetBounds,
                             mTxn->mTargetRotation,
                             mTxn->mTargetOrientation,
                             aRegionToClear);
   info.targetConfig() = targetConfig;
 
   if (!GetTextureForwarder()->IsSameProcess()) {
@@ -772,17 +770,16 @@ ShadowLayerForwarder::EndTransaction(con
 
   if (startTime) {
     mPaintTiming.sendMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds();
     mShadowManager->SendRecordPaintTimes(mPaintTiming);
   }
 
   *aSent = true;
   mIsFirstPaint = false;
-  mPaintSyncId = 0;
   MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
   return true;
 }
 
 RefPtr<CompositableClient>
 ShadowLayerForwarder::FindCompositable(const CompositableHandle& aHandle)
 {
   CompositableClient* client = nullptr;
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -362,18 +362,16 @@ public:
    */
   LayerHandle ConstructShadowFor(ShadowableLayer* aLayer);
 
   /**
    * Flag the next paint as the first for a document.
    */
   void SetIsFirstPaint() { mIsFirstPaint = true; }
 
-  void SetPaintSyncId(int32_t aSyncId) { mPaintSyncId = aSyncId; }
-
   void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
 
   static void PlatformSyncBeforeUpdate();
 
   virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize,
                                       gfxContentType aContent,
                                       SurfaceDescriptor* aBuffer) override;
 
@@ -437,17 +435,16 @@ protected:
 private:
 
   ClientLayerManager* mClientLayerManager;
   Transaction* mTxn;
   MessageLoop* mMessageLoop;
   DiagnosticTypes mDiagnosticTypes;
   bool mIsFirstPaint;
   bool mWindowOverlayChanged;
-  int32_t mPaintSyncId;
   InfallibleTArray<PluginWindowData> mPluginWindowData;
   UniquePtr<ActiveResourceTracker> mActiveResourceTracker;
   uint64_t mNextLayerHandle;
   nsDataHashtable<nsUint64HashKey, CompositableClient*> mCompositables;
   PaintTiming mPaintTiming;
   /**
    * ShadowLayerForwarder might dispatch tasks to main while puppet widget and
    * tabChild don't exist anymore; therefore we hold the event target since its
--- a/gfx/layers/ipc/UiCompositorControllerChild.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerChild.cpp
@@ -1,242 +1,362 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 "UiCompositorControllerChild.h"
-#include "UiCompositorControllerParent.h"
+#include "mozilla/layers/UiCompositorControllerChild.h"
+
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/UiCompositorControllerMessageTypes.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/StaticPtr.h"
+#include "nsBaseWidget.h"
 #include "nsThreadUtils.h"
-#include "mozilla/gfx/GPUProcessManager.h"
+
+#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/widget/AndroidUiThread.h"
+
+static RefPtr<nsThread>
+GetUiThread()
+{
+  return mozilla::GetAndroidUiThread();
+}
+#else
+static RefPtr<nsThread>
+GetUiThread()
+{
+  MOZ_CRASH("Platform does not support UiCompositorController");
+  return nullptr;
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+static bool
+IsOnUiThread()
+{
+  return NS_GetCurrentThread() == GetUiThread();
+}
 
 namespace mozilla {
 namespace layers {
-using namespace gfx;
 
-static bool sInitialized = false;
-static StaticRefPtr<UiCompositorControllerChild> sChild;
-static StaticRefPtr<UiCompositorControllerParent> sParent;
-
-namespace {
-
-struct SurfaceResizeCache {
-  int32_t mSurfaceWidth;
-  int32_t mSurfaceHeight;
-
-  SurfaceResizeCache(int32_t aWidth, int32_t aHeight) :
-    mSurfaceWidth(aWidth),
-    mSurfaceHeight(aHeight) {}
-
-  SurfaceResizeCache(const SurfaceResizeCache& value)
-  {
-    *this = value;
-  }
 
-  SurfaceResizeCache& operator=(const SurfaceResizeCache& value)
-  {
-    mSurfaceWidth = value.mSurfaceWidth;
-    mSurfaceHeight = value.mSurfaceHeight;
-    return *this;
-  }
-
-  SurfaceResizeCache() :
-    mSurfaceWidth(0),
-    mSurfaceHeight(0) {}
-};
-
-static std::map<int64_t, SurfaceResizeCache> sResizeCache;
-
-static void
-DoCachedResize()
+// public:
+/* static */ RefPtr<UiCompositorControllerChild>
+UiCompositorControllerChild::CreateForSameProcess(const int64_t& aRootLayerTreeId)
 {
-  MOZ_ASSERT(sChild);
-  MOZ_ASSERT(sChild->IsOnUiThread());
-
-  for (auto& cache : sResizeCache) {
-    sChild->SendResumeAndResize(cache.first, cache.second.mSurfaceWidth, cache.second.mSurfaceHeight);
-  }
-
-  sResizeCache.clear();
+  RefPtr<UiCompositorControllerChild> child = new UiCompositorControllerChild(0);
+  child->mParent = new UiCompositorControllerParent(aRootLayerTreeId);
+  GetUiThread()->Dispatch(NewRunnableMethod(child, &UiCompositorControllerChild::OpenForSameProcess), nsIThread::DISPATCH_NORMAL);
+  return child;
 }
 
-} // namespace
-
-UiCompositorControllerChild::UiCompositorControllerChild(RefPtr<nsThread> aThread, const uint64_t& aProcessToken)
- : mUiThread(aThread),
-   mProcessToken(aProcessToken)
-{
-}
-
-UiCompositorControllerChild::~UiCompositorControllerChild()
-{
-}
-
-/* static */ UiCompositorControllerChild*
-UiCompositorControllerChild::Get()
-{
-  return sChild;
-}
-
-/* static */ bool
-UiCompositorControllerChild::IsInitialized()
-{
-  return sInitialized;
-}
-
-/* static */ void
-UiCompositorControllerChild::Shutdown()
+/* static */ RefPtr<UiCompositorControllerChild>
+UiCompositorControllerChild::CreateForGPUProcess(const uint64_t& aProcessToken,
+                                                 Endpoint<PUiCompositorControllerChild>&& aEndpoint)
 {
-  RefPtr<UiCompositorControllerChild> child = sChild;
-  if (child) {
-    child->Close();
-    sInitialized = false;
-  }
-}
-
-/* static */ void
-UiCompositorControllerChild::InitSameProcess(RefPtr<nsThread> aThread)
-{
-  MOZ_ASSERT(!sChild);
-  MOZ_ASSERT(!sParent);
-  MOZ_ASSERT(aThread);
-  MOZ_ASSERT(!sInitialized);
-
-  sInitialized = true;
-  RefPtr<UiCompositorControllerChild> child = new UiCompositorControllerChild(aThread, 0);
-  sParent = new UiCompositorControllerParent();
-  aThread->Dispatch(NewRunnableMethod(child, &UiCompositorControllerChild::OpenForSameProcess), nsIThread::DISPATCH_NORMAL);
-}
-
-/* static */ void
-UiCompositorControllerChild::InitWithGPUProcess(RefPtr<nsThread> aThread,
-                                                const uint64_t& aProcessToken,
-                                                Endpoint<PUiCompositorControllerChild>&& aEndpoint)
-{
-  MOZ_ASSERT(!sChild);
-  MOZ_ASSERT(!sParent);
-  MOZ_ASSERT(aThread);
-  MOZ_ASSERT(!sInitialized);
-
-  sInitialized = true;
-  RefPtr<UiCompositorControllerChild> child = new UiCompositorControllerChild(aThread, aProcessToken);
+  RefPtr<UiCompositorControllerChild> child = new UiCompositorControllerChild(aProcessToken);
 
   RefPtr<nsIRunnable> task = NewRunnableMethod<Endpoint<PUiCompositorControllerChild>&&>(
     child, &UiCompositorControllerChild::OpenForGPUProcess, Move(aEndpoint));
 
-  aThread->Dispatch(task.forget(), nsIThread::DISPATCH_NORMAL);
+  GetUiThread()->Dispatch(task.forget(), nsIThread::DISPATCH_NORMAL);
+  return child;
+}
+
+bool
+UiCompositorControllerChild::Pause()
+{
+  if (!mIsOpen) {
+    return false;
+  }
+  return SendPause();
+}
+
+bool
+UiCompositorControllerChild::Resume()
+{
+  if (!mIsOpen) {
+    return false;
+  }
+  return SendResume();
 }
 
-/* static */ void
-UiCompositorControllerChild::CacheSurfaceResize(int64_t aId, int32_t aWidth, int32_t aHeight)
+bool
+UiCompositorControllerChild::ResumeAndResize(const int32_t& aWidth, const int32_t& aHeight)
+{
+  if (!mIsOpen) {
+    mResize = Some(gfx::IntSize(aWidth, aHeight));
+    // Since we are caching these values, pretend the call succeeded.
+    return true;
+  }
+  return SendResumeAndResize(aWidth, aHeight);
+}
+
+bool
+UiCompositorControllerChild::InvalidateAndRender()
 {
-  // This should only be called if the sChild has not been set yet.
-  // It should also only be called from the UI thread but since the sChild hasn't been set
-  // yet, there isn't a good way to verify this.
-  MOZ_ASSERT(!sChild);
-  sResizeCache[aId] = SurfaceResizeCache{aWidth, aHeight};
+  if (!mIsOpen) {
+    return false;
+  }
+  return SendInvalidateAndRender();
+}
+
+bool
+UiCompositorControllerChild::SetMaxToolbarHeight(const int32_t& aHeight)
+{
+  if (!mIsOpen) {
+    mMaxToolbarHeight = Some(aHeight);
+    // Since we are caching this value, pretend the call succeeded.
+    return true;
+  }
+  return SendMaxToolbarHeight(aHeight);
 }
 
-void
-UiCompositorControllerChild::OpenForSameProcess()
+bool
+UiCompositorControllerChild::SetPinned(const bool& aPinned, const int32_t& aReason)
+{
+  if (!mIsOpen) {
+    return false;
+  }
+  return SendPinned(aPinned, aReason);
+}
+
+bool
+UiCompositorControllerChild::ToolbarAnimatorMessageFromUI(const int32_t& aMessage)
 {
-  MOZ_ASSERT(sParent);
-  MOZ_ASSERT(!sChild);
-  MOZ_ASSERT(IsOnUiThread());
+  if (!mIsOpen) {
+    return false;
+  }
+
+  return SendToolbarAnimatorMessageFromUI(aMessage);
+}
 
-  if (!Open(sParent->GetIPCChannel(),
-           mozilla::layers::CompositorThreadHolder::Loop(),
-           mozilla::ipc::ChildSide)) {
-    sParent = nullptr;
-    return;
+bool
+UiCompositorControllerChild::SetDefaultClearColor(const uint32_t& aColor)
+{
+  if (!mIsOpen) {
+    mDefaultClearColor = Some(aColor);
+    // Since we are caching this value, pretend the call succeeded.
+    return true;
   }
 
-  AddRef();
-  sChild = this;
-  DoCachedResize();
+  return SendDefaultClearColor(aColor);
+}
+
+bool
+UiCompositorControllerChild::RequestScreenPixels()
+{
+  if (!mIsOpen) {
+    return false;
+  }
+
+  return SendRequestScreenPixels();
+}
+
+bool
+UiCompositorControllerChild::EnableLayerUpdateNotifications(const bool& aEnable)
+{
+  if (!mIsOpen) {
+    mLayerUpdateEnabled = Some(aEnable);
+    // Since we are caching this value, pretend the call succeeded.
+    return true;
+  }
+
+  return SendEnableLayerUpdateNotifications(aEnable);
+}
+
+bool
+UiCompositorControllerChild::ToolbarPixelsToCompositor(Shmem& aMem, const ScreenIntSize& aSize)
+{
+  if (!mIsOpen) {
+    return false;
+  }
+
+  return SendToolbarPixelsToCompositor(aMem, aSize);
 }
 
 void
-UiCompositorControllerChild::OpenForGPUProcess(Endpoint<PUiCompositorControllerChild>&& aEndpoint)
+UiCompositorControllerChild::Destroy()
 {
-  MOZ_ASSERT(!sChild);
-  MOZ_ASSERT(IsOnUiThread());
-
-  if (!aEndpoint.Bind(this)) {
-    // The GPU Process Manager might be gone if we receive ActorDestroy very
-    // late in shutdown.
-    if (GPUProcessManager* gpm = GPUProcessManager::Get()) {
-      gpm->NotifyRemoteActorDestroyed(mProcessToken);
-    }
+  if (!IsOnUiThread()) {
+    GetUiThread()->Dispatch(NewRunnableMethod(this, &UiCompositorControllerChild::Destroy), nsIThread::DISPATCH_NORMAL);
     return;
   }
 
-  AddRef();
-  sChild = this;
-  DoCachedResize();
+  if (mIsOpen) {
+    // Close the underlying IPC channel.
+    PUiCompositorControllerChild::Close();
+    mIsOpen = false;
+  }
 }
 
 void
-UiCompositorControllerChild::Close()
+UiCompositorControllerChild::SetBaseWidget(nsBaseWidget* aWidget)
 {
-  if (!IsOnUiThread()) {
-    mUiThread->Dispatch(NewRunnableMethod(this, &UiCompositorControllerChild::Close), nsIThread::DISPATCH_NORMAL);
-    return;
-  }
-
-  // We clear mProcessToken when the channel is closed.
-  if (!mProcessToken) {
-    return;
-  }
-
-  // Clear the process token so we don't notify the GPUProcessManager. It already
-  // knows we're closed since it manually called Close, and in fact the GPM could
-  // have already been destroyed during shutdown.
-  mProcessToken = 0;
-  if (this == sChild) {
-    sChild = nullptr;
-  }
-
-  // Close the underlying IPC channel.
-  PUiCompositorControllerChild::Close();
+  mWidget = aWidget;
 }
 
+bool
+UiCompositorControllerChild::AllocPixelBuffer(const int32_t aSize, Shmem* aMem)
+{
+  MOZ_ASSERT(aSize > 0);
+  return AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, aMem);
+}
+
+bool
+UiCompositorControllerChild::DeallocPixelBuffer(Shmem& aMem)
+{
+  return DeallocShmem(aMem);
+}
+
+// protected:
 void
 UiCompositorControllerChild::ActorDestroy(ActorDestroyReason aWhy)
 {
+  mIsOpen = false;
+  mParent = nullptr;
+
   if (mProcessToken) {
-    GPUProcessManager::Get()->NotifyRemoteActorDestroyed(mProcessToken);
+    gfx::GPUProcessManager::Get()->NotifyRemoteActorDestroyed(mProcessToken);
     mProcessToken = 0;
-    sParent = nullptr;
   }
 }
 
 void
 UiCompositorControllerChild::DeallocPUiCompositorControllerChild()
 {
+  if (mParent) {
+    mParent = nullptr;
+  }
   Release();
-  sInitialized = false;
 }
 
 void
 UiCompositorControllerChild::ProcessingError(Result aCode, const char* aReason)
 {
   MOZ_RELEASE_ASSERT(aCode == MsgDropped, "Processing error in UiCompositorControllerChild");
 }
 
 void
 UiCompositorControllerChild::HandleFatalError(const char* aName, const char* aMsg) const
 {
   dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aName, aMsg, OtherPid());
 }
 
-bool
-UiCompositorControllerChild::IsOnUiThread() const
+mozilla::ipc::IPCResult
+UiCompositorControllerChild::RecvToolbarAnimatorMessageFromCompositor(const int32_t& aMessage)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mWidget) {
+    mWidget->RecvToolbarAnimatorMessageFromCompositor(aMessage);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerChild::RecvRootFrameMetrics(const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom, const CSSRect& aPage)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mWidget) {
+    mWidget->UpdateRootFrameMetrics(aScrollOffset, aZoom, aPage);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerChild::RecvScreenPixels(ipc::Shmem&& aMem, const ScreenIntSize& aSize)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mWidget) {
+    mWidget->RecvScreenPixels(Move(aMem), aSize);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+// private:
+UiCompositorControllerChild::UiCompositorControllerChild(const uint64_t& aProcessToken)
+ : mIsOpen(false)
+ , mProcessToken(aProcessToken)
+ , mWidget(nullptr)
+{
+}
+
+UiCompositorControllerChild::~UiCompositorControllerChild()
+{
+}
+
+void
+UiCompositorControllerChild::OpenForSameProcess()
 {
-  return NS_GetCurrentThread() == mUiThread;
+  MOZ_ASSERT(IsOnUiThread());
+
+  mIsOpen = Open(mParent->GetIPCChannel(),
+                 mozilla::layers::CompositorThreadHolder::Loop(),
+                 mozilla::ipc::ChildSide);
+
+  if (!mIsOpen) {
+    mParent = nullptr;
+    return;
+  }
+
+  mParent->InitializeForSameProcess();
+  AddRef();
+  SendCachedValues();
+  // Let Ui thread know the connection is open;
+  RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN);
+}
+
+void
+UiCompositorControllerChild::OpenForGPUProcess(Endpoint<PUiCompositorControllerChild>&& aEndpoint)
+{
+  MOZ_ASSERT(IsOnUiThread());
+
+  mIsOpen = aEndpoint.Bind(this);
+
+  if (!mIsOpen) {
+    // The GPU Process Manager might be gone if we receive ActorDestroy very
+    // late in shutdown.
+    if (gfx::GPUProcessManager* gpm = gfx::GPUProcessManager::Get()) {
+      gpm->NotifyRemoteActorDestroyed(mProcessToken);
+    }
+    return;
+  }
+
+  AddRef();
+  SendCachedValues();
+  // Let Ui thread know the connection is open;
+  RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN);
+}
+
+void
+UiCompositorControllerChild::SendCachedValues()
+{
+  MOZ_ASSERT(mIsOpen);
+  if (mResize) {
+    SendResumeAndResize(mResize.ref().width, mResize.ref().height);
+    mResize.reset();
+  }
+  if (mMaxToolbarHeight) {
+    SendMaxToolbarHeight(mMaxToolbarHeight.ref());
+    mMaxToolbarHeight.reset();
+  }
+  if (mDefaultClearColor) {
+    SendDefaultClearColor(mDefaultClearColor.ref());
+    mDefaultClearColor.reset();
+  }
+  if (mLayerUpdateEnabled) {
+    SendEnableLayerUpdateNotifications(mLayerUpdateEnabled.ref());
+    mLayerUpdateEnabled.reset();
+  }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/UiCompositorControllerChild.h
+++ b/gfx/layers/ipc/UiCompositorControllerChild.h
@@ -2,51 +2,78 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 include_gfx_ipc_UiCompositorControllerChild_h
 #define include_gfx_ipc_UiCompositorControllerChild_h
 
 #include "mozilla/layers/PUiCompositorControllerChild.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ipc/Shmem.h"
+#include "mozilla/layers/UiCompositorControllerParent.h"
 #include "mozilla/RefPtr.h"
-#include <nsThread.h>
+#include "nsThread.h"
+
+class nsBaseWidget;
 
 namespace mozilla {
 namespace layers {
 
-class UiCompositorControllerChild final : public PUiCompositorControllerChild
+class UiCompositorControllerChild final : protected PUiCompositorControllerChild
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UiCompositorControllerChild)
 
-  static bool IsInitialized();
-  static void Shutdown();
-  static UiCompositorControllerChild* Get();
-  static void InitSameProcess(RefPtr<nsThread> aThread);
-  static void InitWithGPUProcess(RefPtr<nsThread> aThread,
-                                 const uint64_t& aProcessToken,
-                                 Endpoint<PUiCompositorControllerChild>&& aEndpoint);
+  static RefPtr<UiCompositorControllerChild> CreateForSameProcess(const int64_t& aRootLayerTreeId);
+  static RefPtr<UiCompositorControllerChild> CreateForGPUProcess(const uint64_t& aProcessToken,
+                                                                 Endpoint<PUiCompositorControllerChild>&& aEndpoint);
 
-  static void CacheSurfaceResize(int64_t aId, int32_t aWidth, int32_t aHeight);
-  void Close();
+  bool Pause();
+  bool Resume();
+  bool ResumeAndResize(const int32_t& aHeight, const int32_t& aWidth);
+  bool InvalidateAndRender();
+  bool SetMaxToolbarHeight(const int32_t& aHeight);
+  bool SetPinned(const bool& aPinned, const int32_t& aReason);
+  bool ToolbarAnimatorMessageFromUI(const int32_t& aMessage);
+  bool SetDefaultClearColor(const uint32_t& aColor);
+  bool RequestScreenPixels();
+  bool EnableLayerUpdateNotifications(const bool& aEnable);
+  bool ToolbarPixelsToCompositor(Shmem& aMem, const ScreenIntSize& aSize);
 
+  void Destroy();
+
+  void SetBaseWidget(nsBaseWidget* aWidget);
+  bool AllocPixelBuffer(const int32_t aSize, Shmem* aMem);
+  bool DeallocPixelBuffer(Shmem& aMem);
+
+protected:
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPUiCompositorControllerChild() override;
   void ProcessingError(Result aCode, const char* aReason) override;
-
   virtual void HandleFatalError(const char* aName, const char* aMsg) const override;
-
-  bool IsOnUiThread() const;
+  mozilla::ipc::IPCResult RecvToolbarAnimatorMessageFromCompositor(const int32_t& aMessage) override;
+  mozilla::ipc::IPCResult RecvRootFrameMetrics(const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom, const CSSRect& aPage) override;
+  mozilla::ipc::IPCResult RecvScreenPixels(ipc::Shmem&& aMem, const ScreenIntSize& aSize) override;
 private:
-  UiCompositorControllerChild(RefPtr<nsThread> aThread, const uint64_t& aProcessToken);
+  explicit UiCompositorControllerChild(const uint64_t& aProcessToken);
   ~UiCompositorControllerChild();
   void OpenForSameProcess();
   void OpenForGPUProcess(Endpoint<PUiCompositorControllerChild>&& aEndpoint);
+  void SendCachedValues();
 
-  RefPtr<nsThread> mUiThread;
+  bool mIsOpen;
   uint64_t mProcessToken;
+  Maybe<gfx::IntSize> mResize;
+  Maybe<int32_t> mMaxToolbarHeight;
+  Maybe<uint32_t> mDefaultClearColor;
+  Maybe<bool> mLayerUpdateEnabled;
+  nsBaseWidget* mWidget;
+  // Should only be set when compositor is in process.
+  RefPtr<UiCompositorControllerParent> mParent;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // include_gfx_ipc_UiCompositorControllerChild_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/UiCompositorControllerMessageTypes.h
@@ -0,0 +1,36 @@
+/* -*- 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 include_gfx_ipc_UiCompositorControllerMessageTypes_h
+#define include_gfx_ipc_UiCompositorControllerMessageTypes_h
+
+namespace mozilla {
+namespace layers {
+
+//
+// NOTE: These values are also defined in mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+//       and must be kept in sync. Any new message added here must also be added there to the AnimatorMessageType enum.
+//
+
+enum UiCompositorControllerMessageTypes {
+  STATIC_TOOLBAR_NEEDS_UPDATE      = 0,  // Sent from compositor when the static toolbar wants to hide.
+  STATIC_TOOLBAR_READY             = 1,  // Sent from compositor when the static toolbar image has been updated and is ready to animate.
+  TOOLBAR_HIDDEN                   = 2,  // Sent to compositor when the real toolbar has been hidden.
+  TOOLBAR_VISIBLE                  = 3,  // Sent to compositor when the real toolbar has been made  visible
+  TOOLBAR_SHOW                     = 4,  // Sent from compositor when the real toolbar should be shown
+  FIRST_PAINT                      = 5,  // Sent from compositor after first paint
+  REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6,  // Sent to the compositor when the snapshot should be shown immediately
+  REQUEST_SHOW_TOOLBAR_ANIMATED    = 7,  // Sent to the compositor when the snapshot should be shown with an animation
+  REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8,  // Sent to the compositor when the snapshot should be hidden immediately
+  REQUEST_HIDE_TOOLBAR_ANIMATED    = 9,  // Sent to the compositor when the snapshot should be hidden with an animation
+  LAYERS_UPDATED                   = 10, // Sent from the compositor when any layer has been updated
+  COMPOSITOR_CONTROLLER_OPEN       = 20  // Compositor controller IPC is open
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // include_gfx_ipc_UiCompositorControllerMessageTypes_h
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -1,118 +1,299 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 "UiCompositorControllerParent.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/Move.h"
+#include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace layers {
 
-RefPtr<UiCompositorControllerParent>
-UiCompositorControllerParent::Start(Endpoint<PUiCompositorControllerParent>&& aEndpoint)
+typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
+
+/* static */ RefPtr<UiCompositorControllerParent>
+UiCompositorControllerParent::GetFromRootLayerTreeId(const uint64_t& aRootLayerTreeId)
 {
-  RefPtr<UiCompositorControllerParent> parent = new UiCompositorControllerParent();
+  LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(aRootLayerTreeId);
+  if (state) {
+    return state->mUiControllerParent;
+  }
+
+  return nullptr;
+}
+
+/* static */ RefPtr<UiCompositorControllerParent>
+UiCompositorControllerParent::Start(const uint64_t& aRootLayerTreeId, Endpoint<PUiCompositorControllerParent>&& aEndpoint)
+{
+  RefPtr<UiCompositorControllerParent> parent = new UiCompositorControllerParent(aRootLayerTreeId);
 
   RefPtr<Runnable> task = NewRunnableMethod<Endpoint<PUiCompositorControllerParent>&&>(
     parent, &UiCompositorControllerParent::Open, Move(aEndpoint));
   CompositorThreadHolder::Loop()->PostTask(task.forget());
 
   return parent;
 }
 
-UiCompositorControllerParent::UiCompositorControllerParent()
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvPause()
+{
+  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+  if (parent) {
+    parent->PauseComposition();
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvResume()
+{
+  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+  if (parent) {
+    parent->ResumeComposition();
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvResumeAndResize(const int32_t& aWidth,
+                                                  const int32_t& aHeight)
+{
+  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+  if (parent) {
+    parent->ResumeCompositionAndResize(aWidth, aHeight);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvInvalidateAndRender()
+{
+  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(mRootLayerTreeId);
+  if (parent) {
+    parent->Invalidate();
+    parent->ScheduleComposition();
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvMaxToolbarHeight(const int32_t& aHeight)
+{
+  mMaxToolbarHeight = aHeight;
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    mAnimator->SetMaxToolbarHeight(mMaxToolbarHeight);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvPinned(const bool& aPinned, const int32_t& aReason)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    mAnimator->SetPinned(aPinned, aReason);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvToolbarAnimatorMessageFromUI(const int32_t& aMessage)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    mAnimator->ToolbarAnimatorMessageFromUI(aMessage);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvDefaultClearColor(const uint32_t& aColor)
+{
+  LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
+
+  if (state && state->mLayerManager) {
+    Compositor* compositor = state->mLayerManager->GetCompositor();
+    if (compositor) {
+      // Android Color is ARGB which is apparently unusual.
+      compositor->SetDefaultClearColor(gfx::Color::UnusualFromARGB(aColor));
+    }
+  }
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvRequestScreenPixels()
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
+
+  if (state && state->mLayerManager && state->mParent) {
+    state->mLayerManager->RequestScreenPixels(this);
+    state->mParent->Invalidate();
+    state->mParent->ScheduleComposition();
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvEnableLayerUpdateNotifications(const bool& aEnable)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    mAnimator->EnableLayersUpdateNotifications(aEnable);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+UiCompositorControllerParent::RecvToolbarPixelsToCompositor(Shmem&& aMem, const ScreenIntSize& aSize)
+{
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    // By adopting the Shmem, the animator is responsible for deallocating.
+    mAnimator->AdoptToolbarPixels(Move(aMem), aSize);
+  } else {
+    DeallocShmem(aMem);
+  }
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
+void
+UiCompositorControllerParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+void
+UiCompositorControllerParent::DeallocPUiCompositorControllerParent()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  Shutdown();
+  Release(); // For AddRef in Initialize()
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void
+UiCompositorControllerParent::RegisterAndroidDynamicToolbarAnimator(AndroidDynamicToolbarAnimator* aAnimator)
+{
+  MOZ_ASSERT(!mAnimator);
+  mAnimator = aAnimator;
+  if (mAnimator) {
+    mAnimator->SetMaxToolbarHeight(mMaxToolbarHeight);
+  }
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+void
+UiCompositorControllerParent::ToolbarAnimatorMessageFromCompositor(int32_t aMessage)
+{
+  // This function can be call from ether compositor or controller thread.
+  if (!CompositorThreadHolder::IsInCompositorThread()) {
+    CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod<int32_t>(this, &UiCompositorControllerParent::ToolbarAnimatorMessageFromCompositor, aMessage));
+    return;
+  }
+
+  Unused << SendToolbarAnimatorMessageFromCompositor(aMessage);
+}
+
+bool
+UiCompositorControllerParent::AllocPixelBuffer(const int32_t aSize, ipc::Shmem* aMem)
+{
+  MOZ_ASSERT(aSize > 0);
+  return AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, aMem);
+}
+
+UiCompositorControllerParent::UiCompositorControllerParent(const uint64_t& aRootLayerTreeId)
+  : mRootLayerTreeId(aRootLayerTreeId)
+  , mMaxToolbarHeight(0)
 {
   MOZ_COUNT_CTOR(UiCompositorControllerParent);
 }
 
 UiCompositorControllerParent::~UiCompositorControllerParent()
 {
   MOZ_COUNT_DTOR(UiCompositorControllerParent);
 }
 
 void
+UiCompositorControllerParent::InitializeForSameProcess()
+{
+  // This function is called by UiCompositorControllerChild in the main thread.
+  // So dispatch to the compositor thread to Initialize.
+  if (!CompositorThreadHolder::IsInCompositorThread()) {
+    CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(this, &UiCompositorControllerParent::InitializeForSameProcess));
+    return;
+  }
+
+  Initialize();
+}
+
+void
+UiCompositorControllerParent::InitializeForOutOfProcess()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  Initialize();
+}
+
+void
+UiCompositorControllerParent::Initialize()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AddRef();
+  LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
+  MOZ_ASSERT(state);
+  MOZ_ASSERT(state->mParent);
+  state->mUiControllerParent = this;
+#if defined(MOZ_WIDGET_ANDROID)
+  state->mParent->GetAPZCTreeManager()->InitializeDynamicToolbarAnimator(mRootLayerTreeId);
+#endif
+}
+
+void
 UiCompositorControllerParent::Open(Endpoint<PUiCompositorControllerParent>&& aEndpoint)
 {
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   if (!aEndpoint.Bind(this)) {
     // We can't recover from this.
     MOZ_CRASH("Failed to bind UiCompositorControllerParent to endpoint");
   }
-  AddRef();
-}
-
-mozilla::ipc::IPCResult
-UiCompositorControllerParent::RecvPause(const uint64_t& aLayersId)
-{
-  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(aLayersId);
-  if (parent) {
-    parent->PauseComposition();
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-UiCompositorControllerParent::RecvResume(const uint64_t& aLayersId)
-{
-  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(aLayersId);
-  if (parent) {
-    parent->ResumeComposition();
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-UiCompositorControllerParent::RecvResumeAndResize(const uint64_t& aLayersId,
-                                                  const int32_t& aHeight,
-                                                  const int32_t& aWidth)
-{
-  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(aLayersId);
-  if (parent) {
-    parent->ResumeCompositionAndResize(aHeight, aWidth);
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-UiCompositorControllerParent::RecvInvalidateAndRender(const uint64_t& aLayersId)
-{
-  CompositorBridgeParent* parent = CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(aLayersId);
-  if (parent) {
-    parent->Invalidate();
-    parent->ScheduleComposition();
-  }
-  return IPC_OK();
+  InitializeForOutOfProcess();
 }
 
 void
 UiCompositorControllerParent::Shutdown()
 {
-  MessageLoop* ccloop = CompositorThreadHolder::Loop();
-  if (MessageLoop::current() != ccloop) {
-    ccloop->PostTask(NewRunnableMethod(this, &UiCompositorControllerParent::ShutdownImpl));
-    return;
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+#if defined(MOZ_WIDGET_ANDROID)
+  if (mAnimator) {
+    mAnimator->Shutdown();
   }
-
-  ShutdownImpl();
-}
-
-void
-UiCompositorControllerParent::ShutdownImpl()
-{
-  Close();
-}
-
-void
-UiCompositorControllerParent::ActorDestroy(ActorDestroyReason aWhy)
-{
-}
-
-void
-UiCompositorControllerParent::DeallocPUiCompositorControllerParent()
-{
-  Release();
+#endif // defined(MOZ_WIDGET_ANDROID)
+  LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
+  if (state) {
+    state->mUiControllerParent = nullptr;
+  }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/UiCompositorControllerParent.h
+++ b/gfx/layers/ipc/UiCompositorControllerParent.h
@@ -1,47 +1,73 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=99: */
 /* 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 include_gfx_ipc_UiCompositorControllerParent_h
 #define include_gfx_ipc_UiCompositorControllerParent_h
 
+#include "mozilla/layers/PUiCompositorControllerParent.h"
+#if defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/layers/AndroidDynamicToolbarAnimator.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+#include "mozilla/ipc/Shmem.h"
 #include "mozilla/RefPtr.h"
-#include "mozilla/layers/PUiCompositorControllerParent.h"
 
 namespace mozilla {
 namespace layers {
 
 class UiCompositorControllerParent final : public PUiCompositorControllerParent
 {
+// UiCompositorControllerChild needs to call the private constructor when running in process.
+friend class UiCompositorControllerChild;
 public:
-  UiCompositorControllerParent();
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UiCompositorControllerParent)
 
-  static RefPtr<UiCompositorControllerParent> Start(Endpoint<PUiCompositorControllerParent>&& aEndpoint);
+  static RefPtr<UiCompositorControllerParent> GetFromRootLayerTreeId(const uint64_t& aRootLayerTreeId);
+  static RefPtr<UiCompositorControllerParent> Start(const uint64_t& aRootLayerTreeId, Endpoint<PUiCompositorControllerParent>&& aEndpoint);
 
-  mozilla::ipc::IPCResult RecvPause(const uint64_t& aLayersId) override;
-  mozilla::ipc::IPCResult RecvResume(const uint64_t& aLayersId) override;
-  mozilla::ipc::IPCResult RecvResumeAndResize(const uint64_t& aLayersId,
-                                              const int32_t& aHeight,
+  // PUiCompositorControllerParent functions
+  mozilla::ipc::IPCResult RecvPause() override;
+  mozilla::ipc::IPCResult RecvResume() override;
+  mozilla::ipc::IPCResult RecvResumeAndResize(const int32_t& aHeight,
                                               const int32_t& aWidth) override;
-  mozilla::ipc::IPCResult RecvInvalidateAndRender(const uint64_t& aLayersId) override;
-
+  mozilla::ipc::IPCResult RecvInvalidateAndRender() override;
+  mozilla::ipc::IPCResult RecvMaxToolbarHeight(const int32_t& aHeight) override;
+  mozilla::ipc::IPCResult RecvPinned(const bool& aPinned, const int32_t& aReason) override;
+  mozilla::ipc::IPCResult RecvToolbarAnimatorMessageFromUI(const int32_t& aMessage) override;
+  mozilla::ipc::IPCResult RecvDefaultClearColor(const uint32_t& aColor) override;
+  mozilla::ipc::IPCResult RecvRequestScreenPixels() override;
+  mozilla::ipc::IPCResult RecvEnableLayerUpdateNotifications(const bool& aEnable) override;
+  mozilla::ipc::IPCResult RecvToolbarPixelsToCompositor(Shmem&& aMem, const ScreenIntSize& aSize) override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void DeallocPUiCompositorControllerParent() override;
 
-  void Shutdown();
+  // Class specific functions
+#if defined(MOZ_WIDGET_ANDROID)
+  void RegisterAndroidDynamicToolbarAnimator(AndroidDynamicToolbarAnimator* aAnimator);
+#endif // MOZ_WIDGET_ANDROID
+  void ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
+  bool AllocPixelBuffer(const int32_t aSize, Shmem* aMem);
 
 private:
+  explicit UiCompositorControllerParent(const uint64_t& aRootLayerTreeId);
   ~UiCompositorControllerParent();
-
+  void InitializeForSameProcess();
+  void InitializeForOutOfProcess();
+  void Initialize();
   void Open(Endpoint<PUiCompositorControllerParent>&& aEndpoint);
-  void ShutdownImpl();
+  void Shutdown();
+
+  uint64_t mRootLayerTreeId;
 
-private:
+#if defined(MOZ_WIDGET_ANDROID)
+  RefPtr<AndroidDynamicToolbarAnimator> mAnimator;
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+  int32_t mMaxToolbarHeight;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // include_gfx_ipc_UiCompositorControllerParent_h
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -173,16 +173,17 @@ EXPORTS.mozilla.layers += [
     'ipc/LayerTreeOwnerTracker.h',
     'ipc/RemoteContentController.h',
     'ipc/ShadowLayers.h',
     'ipc/SharedPlanarYCbCrImage.h',
     'ipc/SharedRGBImage.h',
     'ipc/SynchronousTask.h',
     'ipc/TextureForwarder.h',
     'ipc/UiCompositorControllerChild.h',
+    'ipc/UiCompositorControllerMessageTypes.h',
     'ipc/UiCompositorControllerParent.h',
     'ipc/VideoBridgeChild.h',
     'ipc/VideoBridgeParent.h',
     'LayerAttributes.h',
     'LayerMetricsWrapper.h',
     'LayersTypes.h',
     'opengl/CompositingRenderTargetOGL.h',
     'opengl/CompositorOGL.h',
@@ -240,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',
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -17,20 +17,16 @@
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/layers/WebRenderCompositableHolder.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/widget/CompositorWidget.h"
 
-#if defined(MOZ_WIDGET_ANDROID)
-# include "mozilla/widget/AndroidCompositorWidget.h"
-#endif
-
 bool is_in_main_thread()
 {
   return NS_IsMainThread();
 }
 
 bool is_in_compositor_thread()
 {
   return mozilla::layers::CompositorThreadHolder::IsInCompositorThread();
@@ -138,20 +134,21 @@ WebRenderBridgeParent::RecvCreate(const 
     return IPC_OK();
   }
 
   MOZ_ASSERT(mApi);
 
 #ifdef MOZ_WIDGET_ANDROID
   // XXX temporary hack.
   // XXX Remove it when APZ is supported.
-  widget::AndroidCompositorWidget* widget = mWidget->AsAndroid();
-  if (widget) {
-    widget->SetFirstPaintViewport(LayerIntPoint(0, 0), CSSToLayerScale(), CSSRect(0, 0, aSize.width, aSize.height));
-  }
+  // XXX Broken by Dynamic Toolbar v3. See: Bug 1335895
+//  RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(/* Root Layer Tree ID */);
+//  if (uiController) {
+//    uiController->ToolbarAnimatorMessageFromCompositor(/*FIRST_PAINT*/ 5);
+//  }
 #endif
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvShutdown()
 {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -85,17 +85,16 @@
 #include "plstr.h"
 #include "nsCRT.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "mozilla/gfx/Logging.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "TexturePoolOGL.h"
-#include "mozilla/layers/UiCompositorControllerChild.h"
 #endif
 
 #ifdef USE_SKIA
 # ifdef __GNUC__
 #  pragma GCC diagnostic push
 #  pragma GCC diagnostic ignored "-Wshadow"
 # endif
 # include "skia/include/core/SkGraphics.h"
@@ -985,19 +984,16 @@ gfxPlatform::ShutdownLayersIPC()
         if (gfxPrefs::ChildProcessShutdown()) {
           layers::CompositorBridgeChild::ShutDown();
           layers::ImageBridgeChild::ShutDown();
         }
     } else if (XRE_IsParentProcess()) {
         gfx::VRManagerChild::ShutDown();
         layers::CompositorBridgeChild::ShutDown();
         layers::ImageBridgeChild::ShutDown();
-#if defined(MOZ_WIDGET_ANDROID)
-        layers::UiCompositorControllerChild::Shutdown();
-#endif // defined(MOZ_WIDGET_ANDROID)
         // This has to happen after shutting down the child protocols.
         layers::CompositorThreadHolder::Shutdown();
         if (gfxVars::UseWebRender()) {
             wr::RenderThread::ShutDown();
         }
     } else {
       // TODO: There are other kind of processes and we should make sure gfx
       // stuff is either not created there or shut down properly.
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -336,16 +336,17 @@ private:
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
   DECL_GFX_PREF(Live, "apz.x_stationary_size_multiplier",      APZXStationarySizeMultiplier, float, 3.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_highmem_adjust",            APZYSkateHighMemAdjust, float, 0.0f);
   DECL_GFX_PREF(Live, "apz.y_skate_size_multiplier",           APZYSkateSizeMultiplier, float, 2.5f);
   DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier",      APZYStationarySizeMultiplier, float, 3.5f);
   DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms",        APZZoomAnimationDuration, int32_t, 250);
   DECL_GFX_PREF(Live, "apz.scale_repaint_delay_ms",            APZScaleRepaintDelay, int32_t, 500);
 
+  DECL_GFX_PREF(Live, "browser.ui.scroll-toolbar-threshold",   ToolbarScrollThreshold, int32_t, 10);
   DECL_GFX_PREF(Live, "browser.ui.zoom.force-user-scalable",   ForceUserScalable, bool, false);
   DECL_GFX_PREF(Live, "browser.viewport.desktopWidth",         DesktopViewportWidth, int32_t, 980);
 
   DECL_GFX_PREF(Live, "dom.ipc.plugins.asyncdrawing.enabled",  PluginAsyncDrawingEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.meta-viewport.enabled",             MetaViewportEnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.enabled",                        VREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.autoactivate.enabled",           VRAutoActivateEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -122,23 +122,21 @@ GetPrefsFor(EventClassID aEventClassID)
           "ui.mouse.radius.inputSource.touchOnly", true);
     } else {
       prefs->mTouchOnly = false;
     }
 
     nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
     Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
 
-    Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionEnabled, "ui.zoomedview.enabled", false);
-
-    Preferences::AddBoolVarCache(&prefs->mSimplifiedClusterDetection, "ui.zoomedview.simplified", false);
-
-    Preferences::AddUintVarCache(&prefs->mLimitReadableSize, "ui.zoomedview.limitReadableSize", 8);
-
-    Preferences::AddUintVarCache(&prefs->mKeepLimitSizeForCluster, "ui.zoomedview.keepLimitSize", 16);
+    // These values were formerly set by ui.zoomedview preferences.
+    prefs->mTouchClusterDetectionEnabled = false;
+    prefs->mSimplifiedClusterDetection = false;
+    prefs->mLimitReadableSize = 8;
+    prefs->mKeepLimitSizeForCluster = 16;
   }
 
   return prefs;
 }
 
 static bool
 HasMouseListener(nsIContent* aContent)
 {
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -413,26 +413,20 @@ pref("devtools.remote.wifi.enabled", fal
 pref("font.size.inflation.minTwips", 0);
 
 // When true, zooming will be enabled on all sites, even ones that declare user-scalable=no.
 pref("browser.ui.zoom.force-user-scalable", false);
 
 // When removing this Nightly flag, also remember to remove the flags surrounding this feature
 // in GeckoPreferences and BrowserApp (see bug 1245930).
 #ifdef NIGHTLY_BUILD
-pref("ui.zoomedview.enabled", true);
 pref("ui.bookmark.mobilefolder.enabled", true);
 #else
-pref("ui.zoomedview.enabled", false);
 pref("ui.bookmark.mobilefolder.enabled", false);
 #endif
-pref("ui.zoomedview.keepLimitSize", 16); // value in layer pixels, used to not keep the large elements in the cluster list (Bug 1191041)
-pref("ui.zoomedview.limitReadableSize", 8); // value in layer pixels
-pref("ui.zoomedview.defaultZoomFactor", 2);
-pref("ui.zoomedview.simplified", true); // Do not display all the zoomed view controls, do not use size heurisistic
 
 pref("ui.touch.radius.enabled", false);
 pref("ui.touch.radius.leftmm", 3);
 pref("ui.touch.radius.topmm", 5);
 pref("ui.touch.radius.rightmm", 3);
 pref("ui.touch.radius.bottommm", 2);
 pref("ui.touch.radius.visitedWeight", 120);
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -120,17 +120,20 @@ import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.StrictMode;
@@ -179,17 +182,18 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Vector;
 import java.util.regex.Pattern;
 
 public class BrowserApp extends GeckoApp
                         implements TabsPanel.TabsLayoutChangeListener,
                                    PropertyAnimator.PropertyAnimationListener,
                                    View.OnKeyListener,
-                                   LayerView.DynamicToolbarListener,
+                                   DynamicToolbarAnimator.MetricsListener,
+                                   DynamicToolbarAnimator.ToolbarChromeProxy,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
                                    OnUrlOpenListener,
                                    OnUrlOpenInBackgroundListener,
                                    AnchoredPopup.OnVisibilityChangeListener,
                                    ActionModePresenter,
                                    LayoutInflater.Factory {
     private static final String LOGTAG = "GeckoBrowserApp";
@@ -241,17 +245,16 @@ public class BrowserApp extends GeckoApp
      * Container for the home screen implementation. This will be populated with any valid
      * home screen implementation (currently that is just the HomePager, but that will be extended
      * to permit further experimental replacement panels such as the activity-stream panel).
      */
     private ViewGroup mHomeScreenContainer;
     private int mCachedRecentTabsCount;
     private ActionModeCompat mActionMode;
     private TabHistoryController tabHistoryController;
-    private ZoomedView mZoomedView;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
     public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
 
     private static class MenuItemInfo {
         public int id;
         public String label;
@@ -371,19 +374,16 @@ public class BrowserApp extends GeckoApp
                             VisibilityTransition.IMMEDIATE : VisibilityTransition.ANIMATE;
                     mDynamicToolbar.setVisible(true, transition);
 
                     // The first selection has happened - reset the state.
                     tab.setShouldShowToolbarWithoutAnimationOnFirstSelection(false);
                 }
                 // fall through
             case LOCATION_CHANGE:
-                if (mZoomedView != null) {
-                    mZoomedView.stopZoomDisplay(false);
-                }
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     updateHomePagerForTab(tab);
                 }
 
                 mDynamicToolbar.persistTemporaryVisibility();
                 break;
             case START:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
@@ -1309,34 +1309,24 @@ public class BrowserApp extends GeckoApp
 
         // Intercept key events for gamepad shortcuts
         mBrowserToolbar.setOnKeyListener(this);
     }
 
     private void setDynamicToolbarEnabled(boolean enabled) {
         ThreadUtils.assertOnUiThread();
 
-        if (enabled) {
-            if (mLayerView != null) {
-                mLayerView.getDynamicToolbarAnimator().addTranslationListener(this);
-            }
-            setToolbarMargin(0);
-            mHomeScreenContainer.setPadding(0, mBrowserChrome.getHeight(), 0, 0);
-        } else {
-            // Immediately show the toolbar when disabling the dynamic
-            // toolbar.
-            if (mLayerView != null) {
-                mLayerView.getDynamicToolbarAnimator().removeTranslationListener(this);
-            }
-            mHomeScreenContainer.setPadding(0, 0, 0, 0);
-            if (mBrowserChrome != null) {
-                ViewHelper.setTranslationY(mBrowserChrome, 0);
-            }
-            if (mLayerView != null) {
-                mLayerView.setSurfaceTranslation(0);
+        if (mLayerView != null) {
+            if (enabled) {
+                 mDynamicToolbar.setPinned(false, PinReason.DISABLED);
+            } else {
+               // Immediately show the toolbar when disabling the dynamic
+               // toolbar.
+                mDynamicToolbar.setPinned(true, PinReason.DISABLED);
+                mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
             }
         }
 
         refreshToolbarHeight();
     }
 
     private static boolean isAboutHome(final Tab tab) {
         return AboutPages.isAboutHome(tab.getURL());
@@ -1497,20 +1487,16 @@ public class BrowserApp extends GeckoApp
             mReadingListHelper = null;
         }
 
         if (mAccountsHelper != null) {
             mAccountsHelper.uninit();
             mAccountsHelper = null;
         }
 
-        if (mZoomedView != null) {
-            mZoomedView.destroy();
-        }
-
         mSearchEngineManager.unregisterListeners();
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "Search:Keyword",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
             "Menu:Open",
@@ -1599,16 +1585,20 @@ public class BrowserApp extends GeckoApp
 
     @Override
     protected void initializeChrome() {
         super.initializeChrome();
 
         mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
         mDoorHangerPopup.setOnVisibilityChangeListener(this);
 
+        if (mLayerView != null) {
+            mLayerView.getDynamicToolbarAnimator().addMetricsListener(this);
+            mLayerView.getDynamicToolbarAnimator().setToolbarChromeProxy(this);
+        }
         mDynamicToolbar.setLayerView(mLayerView);
         setDynamicToolbarEnabled(mDynamicToolbar.isEnabled());
 
         // Intercept key events for gamepad shortcuts
         mLayerView.setOnKeyListener(this);
 
         // Initialize the actionbar menu items on startup for both large and small tablets
         if (HardwareUtils.isTablet()) {
@@ -1618,106 +1608,70 @@ public class BrowserApp extends GeckoApp
     }
 
     @Override
     public void onDoorHangerShow() {
         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
         super.onDoorHangerShow();
     }
 
-    private void setToolbarMargin(int margin) {
-        ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
-        mGeckoLayout.requestLayout();
-    }
-
-    @Override
-    public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation) {
-        if (mBrowserChrome == null) {
-            return;
-        }
-
-        final View browserChrome = mBrowserChrome;
-        final ToolbarProgressView progressView = mProgressView;
-
-        ViewHelper.setTranslationY(browserChrome, -aToolbarTranslation);
-        mLayerView.setSurfaceTranslation(mToolbarHeight - aLayerViewTranslation);
-
-        // Stop the progressView from moving all the way up so that we can still see a good chunk of it
-        // when the chrome is offscreen.
-        final float offset = getResources().getDimensionPixelOffset(R.dimen.progress_bar_scroll_offset);
-        final float progressTranslationY = Math.min(aToolbarTranslation, mToolbarHeight - offset);
-        ViewHelper.setTranslationY(progressView, -progressTranslationY);
-
-        if (mFormAssistPopup != null) {
-            mFormAssistPopup.onTranslationChanged();
-        }
-    }
-
     @Override
     public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
         if (isHomePagerVisible() || mBrowserChrome == null) {
             return;
         }
 
         if (mFormAssistPopup != null) {
             mFormAssistPopup.onMetricsChanged(aMetrics);
         }
     }
 
+    // ToolbarChromeProxy inteface
     @Override
-    public void onPanZoomStopped() {
-        if (!mDynamicToolbar.isEnabled() || isHomePagerVisible() ||
-            mBrowserChrome.getVisibility() != View.VISIBLE) {
-            return;
+    public Bitmap getBitmapOfToolbarChrome() {
+        if (mBrowserChrome == null) {
+            return null;
         }
 
-        // Make sure the toolbar is fully hidden or fully shown when the user
-        // lifts their finger, depending on various conditions.
-        ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
-        float toolbarTranslation = mLayerView.getDynamicToolbarAnimator().getToolbarTranslation();
-
-        boolean shortPage = metrics.getPageHeight() < metrics.getHeight();
-        boolean atBottomOfLongPage =
-            FloatUtils.fuzzyEquals(metrics.pageRectBottom, metrics.viewportRectBottom())
-            && (metrics.pageRectBottom > 2 * metrics.getHeight());
-        Log.v(LOGTAG, "On pan/zoom stopped, short page: " + shortPage
-            + "; atBottomOfLongPage: " + atBottomOfLongPage);
-        if (shortPage || atBottomOfLongPage) {
-            mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
+        Bitmap bm = Bitmap.createBitmap(mBrowserChrome.getWidth(), mBrowserChrome.getHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bm);
+        Drawable bgDrawable = mBrowserChrome.getBackground();
+        if (bgDrawable != null) {
+            bgDrawable.draw(canvas);
+        } else {
+            canvas.drawColor(Color.WHITE);
         }
+
+        mBrowserChrome.draw(canvas);
+        return bm;
+    }
+
+    @Override
+    public boolean isToolbarChromeVisible() {
+       return mBrowserChrome.getVisibility() == View.VISIBLE;
+    }
+
+    @Override
+    public void toggleToolbarChrome(final boolean aShow) {
+        toggleChrome(aShow);
     }
 
     public void refreshToolbarHeight() {
         ThreadUtils.assertOnUiThread();
 
         int height = 0;
         if (mBrowserChrome != null) {
             height = mBrowserChrome.getHeight();
         }
 
-        if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
-            // Use aVisibleHeight here so that when the dynamic toolbar is
-            // enabled, the padding will animate with the toolbar becoming
-            // visible.
-            if (mDynamicToolbar.isEnabled()) {
-                // When the dynamic toolbar is enabled, set the padding on the
-                // about:home widget directly - this is to avoid resizing the
-                // LayerView, which can cause visible artifacts.
-                mHomeScreenContainer.setPadding(0, height, 0, 0);
-            } else {
-                setToolbarMargin(height);
-                height = 0;
-            }
-        } else {
-            setToolbarMargin(0);
-        }
+        mHomeScreenContainer.setPadding(0, height, 0, 0);
 
         if (mLayerView != null && height != mToolbarHeight) {
             mToolbarHeight = height;
-            mLayerView.setMaxTranslation(height);
+            mLayerView.setMaxToolbarHeight(height);
             mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
         }
     }
 
     @Override
     void toggleChrome(final boolean aShow) {
         if (aShow) {
             mBrowserChrome.setVisibility(View.VISIBLE);
@@ -1781,21 +1735,16 @@ public class BrowserApp extends GeckoApp
                 break;
 
             case "Gecko:DelayedStartup":
                 EventDispatcher.getInstance().unregisterUiThreadListener(this, "Gecko:DelayedStartup");
 
                 // Force tabs panel inflation once the initial pageload is finished.
                 ensureTabsPanelExists();
 
-                if (AppConstants.NIGHTLY_BUILD && mZoomedView == null) {
-                    ViewStub stub = (ViewStub) findViewById(R.id.zoomed_view_stub);
-                    mZoomedView = (ZoomedView) stub.inflate();
-                }
-
                 if (AppConstants.MOZ_MEDIA_PLAYER) {
                     // Check if the fragment is already added. This should never be true
                     // here, but this is a nice safety check. If casting is disabled,
                     // these classes aren't built. We use reflection to initialize them.
                     final Class<?> mediaManagerClass = getMediaPlayerManager();
 
                     if (mediaManagerClass != null) {
                         try {
@@ -3297,31 +3246,21 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public void setFullScreen(final boolean fullscreen) {
         super.setFullScreen(fullscreen);
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 if (fullscreen) {
-                    if (mDynamicToolbar.isEnabled()) {
-                        mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
-                        mDynamicToolbar.setPinned(true, PinReason.FULL_SCREEN);
-                    } else {
-                        setToolbarMargin(0);
-                    }
-                    mBrowserChrome.setVisibility(View.GONE);
+                    mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
+                    mDynamicToolbar.setPinned(true, PinReason.FULL_SCREEN);
                 } else {
-                    mBrowserChrome.setVisibility(View.VISIBLE);
-                    if (mDynamicToolbar.isEnabled()) {
-                        mDynamicToolbar.setPinned(false, PinReason.FULL_SCREEN);
-                        mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
-                    } else {
-                        setToolbarMargin(mBrowserChrome.getHeight());
-                    }
+                    mDynamicToolbar.setPinned(false, PinReason.FULL_SCREEN);
+                    mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
                 }
             }
         });
     }
 
     @Override
     public boolean onPrepareOptionsMenu(Menu aMenu) {
         if (aMenu == null)
@@ -4092,17 +4031,17 @@ public class BrowserApp extends GeckoApp
     @Override
     public void startActionMode(final ActionModeCompat.Callback callback) {
         // If actionMode is null, we're not currently showing one. Flip to the action mode view
         if (mActionMode == null) {
             mActionBarFlipper.showNext();
             DynamicToolbarAnimator toolbar = mLayerView.getDynamicToolbarAnimator();
 
             // If the toolbar is dynamic and not currently showing, just slide it in
-            if (mDynamicToolbar.isEnabled() && toolbar.getToolbarTranslation() != 0) {
+            if (mDynamicToolbar.isEnabled() && toolbar.getCurrentToolbarHeight() == 0) {
                 mDynamicToolbar.setTemporarilyVisible(true, VisibilityTransition.ANIMATE);
             }
             mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
 
         } else {
             // Otherwise, we're already showing an action mode. Just finish it and show the new one
             mActionMode.finish();
         }
--- a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
@@ -272,17 +272,17 @@ public class FormAssistPopup extends Rel
             sValidationMessageHeight = (int) (res.getDimension(R.dimen.validation_message_height));
         }
 
         float zoom = aMetrics.zoomFactor;
 
         // These values correspond to the input box for which we want to
         // display the FormAssistPopup.
         int left = (int) (mX * zoom - aMetrics.viewportRectLeft);
-        int top = (int) (mY * zoom - aMetrics.viewportRectTop + GeckoAppShell.getLayerView().getSurfaceTranslation());
+        int top = (int) (mY * zoom - aMetrics.viewportRectTop + GeckoAppShell.getLayerView().getCurrentToolbarHeight());
         int width = (int) (mW * zoom);
         int height = (int) (mH * zoom);
 
         int popupWidth = LayoutParams.MATCH_PARENT;
         int popupLeft = left < 0 ? 0 : left;
 
         FloatSize viewport = aMetrics.getSize();
 
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1778,19 +1778,16 @@ public abstract class GeckoApp
         if (mIsRestoringActivity) {
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
             if (selectedTab != null) {
                 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
             }
 
             if (GeckoThread.isRunning()) {
                 geckoConnected();
-                if (mLayerView != null) {
-                    mLayerView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
-                }
             }
         }
     }
 
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void onGlobalLayout() {
         if (Versions.preJB) {
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
@@ -798,17 +798,16 @@ public class Tabs implements BundleEvent
 
             // When one tab is deselected, another one is always selected, so only
             // queue a single persist operation. When tabs are added/closed, they
             // are also selected/unselected, so it would be redundant to also listen
             // for ADDED/CLOSED events.
             case SELECTED:
                 if (mLayerView != null) {
                     mLayerView.setSurfaceBackgroundColor(getTabColor(tab));
-                    mLayerView.setPaintState(LayerView.PAINT_START);
                 }
                 queuePersistAllTabs();
             case UNSELECTED:
                 tab.onChange();
                 break;
             default:
                 break;
         }
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
+++ /dev/null
@@ -1,839 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.gfx.PanZoomController;
-import org.mozilla.gecko.gfx.PointUtils;
-import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-
-public class ZoomedView extends FrameLayout implements LayerView.DynamicToolbarListener,
-        LayerView.ZoomedViewListener, BundleEventListener {
-    private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
-
-    private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 1.5f};
-    private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
-    private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
-    private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
-    private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
-    private static final int OPENING_ANIMATION_DURATION_MS = 250;
-    private static final int CLOSING_ANIMATION_DURATION_MS = 150;
-    private static final float OVERSHOOT_INTERPOLATOR_TENSION = 1.5f;
-
-    private float zoomFactor;
-    private int currentZoomFactorIndex;
-    private boolean isSimplifiedUI;
-    private int defaultZoomFactor;
-    private PrefsHelper.PrefHandler prefObserver;
-
-    private ImageView zoomedImageView;
-    private LayerView layerView;
-    private int viewWidth;
-    private int viewHeight; // Only the zoomed view height, no toolbar, no shadow ...
-    private int viewContainerWidth;
-    private int viewContainerHeight; // Zoomed view height with toolbar and other elements like shadow, ...
-    private int containterSize; // shadow, margin, ...
-    private Point lastPosition;
-    private boolean shouldSetVisibleOnUpdate;
-    private boolean isBlockedFromAppearing; // Prevent the display of the zoomedview while FormAssistantPopup is visible
-    private PointF returnValue;
-    private final PointF animationStart;
-    private ImageView closeButton;
-    private TextView changeZoomFactorButton;
-    private boolean toolbarOnTop;
-    private float offsetDueToToolBarPosition;
-    private int toolbarHeight;
-    private int cornerRadius;
-    private float dynamicToolbarOverlap;
-
-    private boolean stopUpdateView;
-
-    private int lastOrientation;
-
-    private ByteBuffer buffer;
-    private Runnable requestRenderRunnable;
-    private long startTimeReRender;
-    private long lastStartTimeReRender;
-
-    private ZoomedViewTouchListener touchListener;
-
-    private enum StartPointUpdate {
-        GECKO_POSITION, CENTER, NO_CHANGE
-    }
-
-    private class RoundedBitmapDrawable extends BitmapDrawable {
-        private Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
-        final float cornerRadius;
-        final boolean squareOnTopOfDrawable;
-
-        RoundedBitmapDrawable(Resources res, Bitmap bitmap, boolean squareOnTop, int radius) {
-            super(res, bitmap);
-            squareOnTopOfDrawable = squareOnTop;
-            final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
-                Shader.TileMode.CLAMP);
-            paint.setAntiAlias(true);
-            paint.setShader(shader);
-            cornerRadius = radius;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            int height = getBounds().height();
-            int width = getBounds().width();
-            RectF rect = new RectF(0.0f, 0.0f, width, height);
-            canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
-
-            //draw rectangles over the corners we want to be square
-            if (squareOnTopOfDrawable) {
-                canvas.drawRect(0, 0, cornerRadius, cornerRadius, paint);
-                canvas.drawRect(width - cornerRadius, 0, width, cornerRadius, paint);
-            } else {
-                canvas.drawRect(0, height - cornerRadius, cornerRadius, height, paint);
-                canvas.drawRect(width - cornerRadius, height - cornerRadius, width, height, paint);
-            }
-        }
-    }
-
-    private class ZoomedViewTouchListener implements View.OnTouchListener {
-        private float originRawX;
-        private float originRawY;
-        private boolean dragged;
-        private MotionEvent actionDownEvent;
-
-        @Override
-        public boolean onTouch(View view, MotionEvent event) {
-            if (layerView == null) {
-                return false;
-            }
-
-            switch (event.getAction()) {
-            case MotionEvent.ACTION_MOVE:
-                if (moveZoomedView(event)) {
-                    dragged = true;
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-                if (dragged) {
-                    dragged = false;
-                } else {
-                    if (isClickInZoomedView(event.getY())) {
-                        GeckoAppShell.notifyObservers("Gesture:ClickInZoomedView", "");
-                        layerView.dispatchTouchEvent(actionDownEvent);
-                        actionDownEvent.recycle();
-                        PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
-                        // the LayerView expects the coordinates relative to the window, not the surface, so we need
-                        // to adjust that here.
-                        convertedPosition.y += layerView.getSurfaceTranslation();
-                        MotionEvent e = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
-                                MotionEvent.ACTION_UP, convertedPosition.x, convertedPosition.y,
-                                event.getMetaState());
-                        layerView.dispatchTouchEvent(e);
-                        e.recycle();
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_DOWN:
-                dragged = false;
-                originRawX = event.getRawX();
-                originRawY = event.getRawY();
-                PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
-                // the LayerView expects the coordinates relative to the window, not the surface, so we need
-                // to adjust that here.
-                convertedPosition.y += layerView.getSurfaceTranslation();
-                actionDownEvent = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
-                        MotionEvent.ACTION_DOWN, convertedPosition.x, convertedPosition.y,
-                        event.getMetaState());
-                break;
-            }
-            return true;
-        }
-
-        private boolean isClickInZoomedView(float y) {
-            return ((toolbarOnTop && y > toolbarHeight) ||
-                (!toolbarOnTop && y < ZoomedView.this.viewHeight));
-        }
-
-        private boolean moveZoomedView(MotionEvent event) {
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) ZoomedView.this.getLayoutParams();
-            if ((!dragged) && (Math.abs((int) (event.getRawX() - originRawX)) < PanZoomController.CLICK_THRESHOLD)
-                    && (Math.abs((int) (event.getRawY() - originRawY)) < PanZoomController.CLICK_THRESHOLD)) {
-                // When the user just touches the screen ACTION_MOVE can be detected for a very small delta on position.
-                // In this case, the move is ignored if the delta is lower than 1 unit.
-                return false;
-            }
-
-            float newLeftMargin = params.leftMargin + event.getRawX() - originRawX;
-            float newTopMargin = params.topMargin + event.getRawY() - originRawY;
-            ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-            ZoomedView.this.moveZoomedView(metrics, newLeftMargin, newTopMargin, StartPointUpdate.CENTER);
-            originRawX = event.getRawX();
-            originRawY = event.getRawY();
-            return true;
-        }
-    }
-
-    public ZoomedView(Context context) {
-        this(context, null, 0);
-    }
-
-    public ZoomedView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        isSimplifiedUI = true;
-        isBlockedFromAppearing = false;
-        getPrefs();
-        currentZoomFactorIndex = 0;
-        returnValue = new PointF();
-        animationStart = new PointF();
-        requestRenderRunnable = new Runnable() {
-            @Override
-            public void run() {
-                requestZoomedViewRender();
-            }
-        };
-        touchListener = new ZoomedViewTouchListener();
-    }
-
-    @Override // View
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        GeckoApp.getEventDispatcher().registerUiThreadListener(this,
-                "FormAssist:AutoCompleteResult",
-                "FormAssist:Hide",
-                "Gesture:CloseZoomedView",
-                "Gesture:ClusteredLinksClicked");
-    }
-
-    @Override // View
-    public void onDetachedFromWindow() {
-        GeckoApp.getEventDispatcher().unregisterUiThreadListener(this,
-                "FormAssist:AutoCompleteResult",
-                "FormAssist:Hide",
-                "Gesture:CloseZoomedView",
-                "Gesture:ClusteredLinksClicked");
-
-        super.onDetachedFromWindow();
-    }
-
-    void destroy() {
-        if (prefObserver != null) {
-            PrefsHelper.removeObserver(prefObserver);
-            prefObserver = null;
-        }
-        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-    }
-
-    // This method (onFinishInflate) is called only when the zoomed view class is used inside
-    // an xml structure <org.mozilla.gecko.ZoomedView ...
-    // It won't be called if the class is used from java code like "new  ZoomedView(context);"
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        closeButton = (ImageView) findViewById(R.id.dialog_close);
-        changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
-        zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
-
-        updateUI();
-
-        toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
-        containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
-        cornerRadius = getResources().getDimensionPixelSize(R.dimen.standard_corner_radius);
-
-        moveToolbar(true);
-    }
-
-    private void setListeners() {
-        closeButton.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View view) {
-                stopZoomDisplay(true);
-            }
-        });
-
-        changeZoomFactorButton.setOnTouchListener(new  OnTouchListener() {
-            public boolean onTouch(View v, MotionEvent event) {
-
-                if (event.getAction() == MotionEvent.ACTION_UP) {
-                    if (event.getX() >= (changeZoomFactorButton.getLeft() + changeZoomFactorButton.getWidth() / 2)) {
-                        changeZoomFactor(true);
-                    } else {
-                        changeZoomFactor(false);
-                    }
-                }
-                return true;
-            }
-        });
-
-        setOnTouchListener(touchListener);
-    }
-
-    private void removeListeners() {
-        closeButton.setOnClickListener(null);
-
-        changeZoomFactorButton.setOnTouchListener(null);
-
-        setOnTouchListener(null);
-    }
-    /*
-     * Convert a click from ZoomedView. Return the position of the click in the
-     * LayerView
-     */
-    private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        final float parentWidth = metrics.getWidth();
-        final float parentHeight = metrics.getHeight();
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
-
-        // The number of unzoomed content pixels that can be displayed in the
-        // zoomed area.
-        float visibleContentPixels = viewWidth / zoomFactor;
-        // The offset in content pixels of the leftmost zoomed pixel from the
-        // layerview's left edge when the zoomed view is moved to the right as
-        // far as it can go.
-        float maxContentOffset = parentWidth - visibleContentPixels;
-        // The maximum offset in screen pixels that the zoomed view can have
-        float maxZoomedViewOffset = parentWidth - viewContainerWidth;
-
-        // The above values allow us to compute the term
-        //   maxContentOffset / maxZoomedViewOffset
-        // which is the number of content pixels that we should move over by
-        // for every screen pixel that the zoomed view is moved over by.
-        // This allows a smooth transition from when the zoomed view is at the
-        // leftmost extent to when it is at the rightmost extent.
-
-        // This is the offset in content pixels of the leftmost zoomed pixel
-        // visible in the zoomed view. This value is relative to the layerview
-        // edge.
-        float zoomedContentOffset = ((float)params.leftMargin) * maxContentOffset / maxZoomedViewOffset;
-        returnValue.x = (int)(zoomedContentOffset + (x / zoomFactor));
-
-        // Same comments here vertically
-        visibleContentPixels = viewHeight / zoomFactor;
-        maxContentOffset = parentHeight - visibleContentPixels;
-        maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight);
-        float zoomedAreaOffset = (float)params.topMargin + offsetDueToToolBarPosition - layerView.getSurfaceTranslation();
-        zoomedContentOffset = zoomedAreaOffset * maxContentOffset / maxZoomedViewOffset;
-        returnValue.y = (int)(zoomedContentOffset + ((y - offsetDueToToolBarPosition) / zoomFactor));
-
-        return returnValue;
-    }
-
-    /*
-     * A touch point (x,y) occurs in LayerView, this point should be displayed
-     * in the center of the zoomed view. The returned point is the position of
-     * the Top-Left zoomed view point on the screen device
-     */
-    private PointF getZoomedViewTopLeftPositionFromTouchPosition(float x, float y) {
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        final float parentWidth = metrics.getWidth();
-        final float parentHeight = metrics.getHeight();
-
-        // See comments in getUnzoomedPositionFromPointInZoomedView, but the
-        // transformations here are largely the reverse of that function.
-
-        float visibleContentPixels = viewWidth / zoomFactor;
-        float maxContentOffset = parentWidth - visibleContentPixels;
-        float maxZoomedViewOffset = parentWidth - viewContainerWidth;
-        float contentPixelOffset = x - (visibleContentPixels / 2.0f);
-        returnValue.x = (int)(contentPixelOffset * (maxZoomedViewOffset / maxContentOffset));
-
-        visibleContentPixels = viewHeight / zoomFactor;
-        maxContentOffset = parentHeight - visibleContentPixels;
-        maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight);
-        contentPixelOffset = y - (visibleContentPixels / 2.0f);
-        float unscaledViewOffset = layerView.getSurfaceTranslation() - offsetDueToToolBarPosition;
-        returnValue.y = (int)((contentPixelOffset * (maxZoomedViewOffset / maxContentOffset)) + unscaledViewOffset);
-
-        return returnValue;
-    }
-
-    private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin,
-            StartPointUpdate animateStartPoint) {
-        RelativeLayout.LayoutParams newLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
-        newLayoutParams.leftMargin = (int) newLeftMargin;
-        newLayoutParams.topMargin = (int) newTopMargin;
-        int topMarginMin = (int)(layerView.getSurfaceTranslation() + dynamicToolbarOverlap);
-        int topMarginMax = layerView.getHeight() - viewContainerHeight;
-        int leftMarginMin = 0;
-        int leftMarginMax = layerView.getWidth() - viewContainerWidth;
-
-        if (newTopMargin < topMarginMin) {
-            newLayoutParams.topMargin = topMarginMin;
-        } else if (newTopMargin > topMarginMax) {
-            newLayoutParams.topMargin = topMarginMax;
-        }
-
-        if (newLeftMargin < leftMarginMin) {
-            newLayoutParams.leftMargin = leftMarginMin;
-        } else if (newLeftMargin > leftMarginMax) {
-            newLayoutParams.leftMargin = leftMarginMax;
-        }
-
-        if (newLayoutParams.topMargin < topMarginMin + 1) {
-            moveToolbar(false);
-        } else if (newLayoutParams.topMargin > topMarginMax - 1) {
-            moveToolbar(true);
-        }
-
-        if (animateStartPoint == StartPointUpdate.GECKO_POSITION) {
-            // Before this point, the animationStart point is relative to the layerView.
-            // The value is initialized in startZoomDisplay using the click point position coming from Gecko.
-            // The position of the zoomed view is now calculated, so the position of the animation
-            // can now be correctly set relative to the zoomed view
-            animationStart.x = animationStart.x - newLayoutParams.leftMargin;
-            animationStart.y = animationStart.y - newLayoutParams.topMargin;
-        } else if (animateStartPoint == StartPointUpdate.CENTER) {
-            // At this point, the animationStart point is no more valid probably because
-            // the zoomed view has been moved by the user.
-            // In this case, the animationStart point is set to the center point of the zoomed view.
-            PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(viewContainerWidth / 2, viewContainerHeight / 2);
-            animationStart.x = convertedPosition.x - newLayoutParams.leftMargin;
-            animationStart.y = convertedPosition.y - newLayoutParams.topMargin;
-        }
-
-        setLayoutParams(newLayoutParams);
-        PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(0, offsetDueToToolBarPosition);
-        lastPosition = PointUtils.round(convertedPosition);
-        requestZoomedViewRender();
-    }
-
-    private void moveToolbar(boolean moveTop) {
-        if (toolbarOnTop == moveTop) {
-            return;
-        }
-        toolbarOnTop = moveTop;
-        if (toolbarOnTop) {
-            offsetDueToToolBarPosition = toolbarHeight;
-        } else {
-            offsetDueToToolBarPosition = 0;
-        }
-
-        RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) zoomedImageView.getLayoutParams();
-        RelativeLayout.LayoutParams pChangeZoomFactorButton = (RelativeLayout.LayoutParams) changeZoomFactorButton.getLayoutParams();
-        RelativeLayout.LayoutParams pCloseButton = (RelativeLayout.LayoutParams) closeButton.getLayoutParams();
-
-        if (moveTop) {
-            p.addRule(RelativeLayout.BELOW, R.id.change_zoom_factor);
-            pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, 0);
-            pCloseButton.addRule(RelativeLayout.BELOW, 0);
-        } else {
-            p.addRule(RelativeLayout.BELOW, 0);
-            pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
-            pCloseButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
-        }
-        pChangeZoomFactorButton.addRule(RelativeLayout.ALIGN_LEFT, R.id.zoomed_image_view);
-        pCloseButton.addRule(RelativeLayout.ALIGN_RIGHT, R.id.zoomed_image_view);
-        zoomedImageView.setLayoutParams(p);
-        changeZoomFactorButton.setLayoutParams(pChangeZoomFactorButton);
-        closeButton.setLayoutParams(pCloseButton);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        // In case of orientation change, the zoomed view update is stopped until the orientation change
-        // is completed. At this time, the function onMetricsChanged is called and the
-        // zoomed view update is restarted again.
-        if (lastOrientation != newConfig.orientation) {
-            shouldBlockUpdate(true);
-            lastOrientation = newConfig.orientation;
-        }
-    }
-
-    private void refreshZoomedViewSize(ImmutableViewportMetrics viewport) {
-        if (layerView == null) {
-            return;
-        }
-
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
-        setCapturedSize(viewport);
-        moveZoomedView(viewport, params.leftMargin, params.topMargin, StartPointUpdate.NO_CHANGE);
-    }
-
-    private void setCapturedSize(ImmutableViewportMetrics metrics) {
-        float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight());
-        viewWidth = (int) ((parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
-        viewHeight = (int) ((parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
-        viewContainerHeight = viewHeight + toolbarHeight +
-                2 * containterSize; // Top and bottom shadows
-        viewContainerWidth = viewWidth +
-                2 * containterSize; // Right and left shadows
-        // Display in zoomedview is corrupted when width is an odd number
-        // More details about this issue here: bug 776906 comment 11
-        viewWidth &= ~0x1;
-    }
-
-    private void shouldBlockUpdate(boolean shouldBlockUpdate) {
-        stopUpdateView = shouldBlockUpdate;
-    }
-
-    private Bitmap.Config getBitmapConfig() {
-        return (GeckoAppShell.getScreenDepth() == 24) ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
-    }
-
-    private void updateUI() {
-        // onFinishInflate is not yet completed, the update of the UI will be done later
-        if (changeZoomFactorButton == null) {
-            return;
-        }
-        if (isSimplifiedUI) {
-            changeZoomFactorButton.setVisibility(View.INVISIBLE);
-        } else {
-            setTextInZoomFactorButton(zoomFactor);
-            changeZoomFactorButton.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void getPrefs() {
-        prefObserver = new PrefsHelper.PrefHandlerBase() {
-            @Override
-            public void prefValue(String pref, boolean simplified) {
-                isSimplifiedUI = simplified;
-                if (simplified) {
-                    zoomFactor = (float) defaultZoomFactor;
-                } else {
-                    zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
-                }
-                updateUI();
-            }
-
-            @Override
-            public void prefValue(String pref, int defaultZoomFactorFromSettings) {
-                defaultZoomFactor = defaultZoomFactorFromSettings;
-                if (isSimplifiedUI) {
-                    zoomFactor = (float) defaultZoomFactor;
-                } else {
-                    zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
-                }
-                updateUI();
-            }
-        };
-        PrefsHelper.addObserver(new String[] { "ui.zoomedview.simplified",
-                                               "ui.zoomedview.defaultZoomFactor" },
-                                prefObserver);
-    }
-
-    private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
-        if (isBlockedFromAppearing) {
-            return;
-        }
-        if (layerView == null) {
-            layerView = aLayerView;
-            layerView.addZoomedViewListener(this);
-            layerView.getDynamicToolbarAnimator().addTranslationListener(this);
-            ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-            setCapturedSize(metrics);
-        }
-        startTimeReRender = 0;
-        shouldSetVisibleOnUpdate = true;
-
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        // At this point, the start point is relative to the layerView.
-        // Later, it will be converted relative to the zoomed view as soon as
-        // the position of the zoomed view will be calculated.
-        animationStart.x = (float) leftFromGecko * metrics.zoomFactor;
-        animationStart.y = (float) topFromGecko * metrics.zoomFactor + layerView.getSurfaceTranslation();
-
-        moveUsingGeckoPosition(leftFromGecko, topFromGecko);
-    }
-
-    public void stopZoomDisplay(boolean withAnimation) {
-        // If "startZoomDisplay" is running and not totally completed (Gecko thread is still
-        // running and "showZoomedView" has not yet been called), the zoomed view will be
-        // displayed after this call and it should not.
-        // Force the stop of the zoomed view, changing the shouldSetVisibleOnUpdate flag
-        // before the test of the visibility.
-        shouldSetVisibleOnUpdate = false;
-        if (getVisibility() == View.VISIBLE) {
-            hideZoomedView(withAnimation);
-            ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-            if (layerView != null) {
-                layerView.getDynamicToolbarAnimator().removeTranslationListener(this);
-                layerView.removeZoomedViewListener(this);
-                layerView = null;
-            }
-        }
-    }
-
-    private void changeZoomFactor(boolean zoomIn) {
-        if (zoomIn && currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
-            currentZoomFactorIndex++;
-        } else if (zoomIn && currentZoomFactorIndex >= ZOOM_FACTORS_LIST.length - 1) {
-            currentZoomFactorIndex = 0;
-        } else if (!zoomIn && currentZoomFactorIndex > 0) {
-            currentZoomFactorIndex--;
-        } else {
-            currentZoomFactorIndex = ZOOM_FACTORS_LIST.length - 1;
-        }
-        zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
-
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        refreshZoomedViewSize(metrics);
-        setTextInZoomFactorButton(zoomFactor);
-    }
-
-    private void setTextInZoomFactorButton(float zoom) {
-        final String percentageValue = Integer.toString((int) (100 * zoom));
-        changeZoomFactorButton.setText("- " + getResources().getString(R.string.percent, percentageValue) + " +");
-    }
-
-    @Override // BundleEventListener
-    public void handleMessage(final String event, final GeckoBundle message,
-                              final EventCallback callback) {
-        if ("Gesture:CloseZoomedView".equals(event)) {
-            stopZoomDisplay(message.getBoolean("animate"));
-
-        } else if ("Gesture:ClusteredLinksClicked".equals(event)) {
-            final GeckoBundle clickPosition = message.getBundle("clickPosition");
-            final int left = clickPosition.getInt("x");
-            final int top = clickPosition.getInt("y");
-            // Start to display inside the zoomedView
-            final LayerView geckoAppLayerView = GeckoAppShell.getLayerView();
-            if (geckoAppLayerView != null) {
-                startZoomDisplay(geckoAppLayerView, left, top);
-            }
-
-        } else if ("FormAssist:AutoCompleteResult".equals(event)) {
-            isBlockedFromAppearing = true;
-            stopZoomDisplay(true);
-
-        } else if ("FormAssist:Hide".equals(event)) {
-            isBlockedFromAppearing = false;
-        }
-    }
-
-    private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) {
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        final float parentHeight = metrics.getHeight();
-        // moveToolbar is called before getZoomedViewTopLeftPositionFromTouchPosition in order to
-        // correctly center vertically the zoomed area
-        moveToolbar((topFromGecko * metrics.zoomFactor > parentHeight / 2));
-        PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition((leftFromGecko * metrics.zoomFactor),
-                (topFromGecko * metrics.zoomFactor));
-        moveZoomedView(metrics, convertedPosition.x, convertedPosition.y, StartPointUpdate.GECKO_POSITION);
-    }
-
-    @Override
-    public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation) {
-        ThreadUtils.assertOnUiThread();
-        if (layerView != null) {
-            dynamicToolbarOverlap = aLayerViewTranslation - aToolbarTranslation;
-            refreshZoomedViewSize(layerView.getViewportMetrics());
-        }
-    }
-
-    @Override
-    public void onMetricsChanged(final ImmutableViewportMetrics viewport) {
-        // It can be called from a Gecko thread (forceViewportMetrics in GeckoLayerClient).
-        // Post to UI Thread to avoid Exception:
-        //    "Only the original thread that created a view hierarchy can touch its views."
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                shouldBlockUpdate(false);
-                refreshZoomedViewSize(viewport);
-            }
-        });
-    }
-
-    @Override
-    public void onPanZoomStopped() {
-    }
-
-    @Override
-    public void updateView(ByteBuffer data) {
-        final Bitmap sb3 = Bitmap.createBitmap(viewWidth, viewHeight, getBitmapConfig());
-        if (sb3 != null) {
-            data.rewind();
-            try {
-                sb3.copyPixelsFromBuffer(data);
-            } catch (Exception iae) {
-                Log.w(LOGTAG, iae.toString());
-            }
-            if (zoomedImageView != null) {
-                RoundedBitmapDrawable ob3 = new RoundedBitmapDrawable(getResources(), sb3, toolbarOnTop, cornerRadius);
-                zoomedImageView.setImageDrawable(ob3);
-            }
-        }
-        if (shouldSetVisibleOnUpdate) {
-            this.showZoomedView();
-        }
-        lastStartTimeReRender = startTimeReRender;
-        startTimeReRender = 0;
-    }
-
-    private void showZoomedView() {
-        // no animation if the zoomed view is already visible
-        if (getVisibility() != View.VISIBLE) {
-            final Animation anim = new ScaleAnimation(
-                    0f, 1f, // Start and end values for the X axis scaling
-                    0f, 1f, // Start and end values for the Y axis scaling
-                    Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
-                    Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
-            anim.setFillAfter(true); // Needed to keep the result of the animation
-            anim.setDuration(OPENING_ANIMATION_DURATION_MS);
-            anim.setInterpolator(new OvershootInterpolator(OVERSHOOT_INTERPOLATOR_TENSION));
-            anim.setAnimationListener(new AnimationListener() {
-                public void onAnimationEnd(Animation animation) {
-                    setListeners();
-                }
-                public void onAnimationRepeat(Animation animation) {
-                }
-                public void onAnimationStart(Animation animation) {
-                    removeListeners();
-                }
-            });
-            setAnimation(anim);
-        }
-        setVisibility(View.VISIBLE);
-        shouldSetVisibleOnUpdate = false;
-    }
-
-    private void hideZoomedView(boolean withAnimation) {
-        if (withAnimation) {
-            final Animation anim = new ScaleAnimation(
-                1f, 0f, // Start and end values for the X axis scaling
-                1f, 0f, // Start and end values for the Y axis scaling
-                Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
-                Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
-            anim.setFillAfter(true); // Needed to keep the result of the animation
-            anim.setDuration(CLOSING_ANIMATION_DURATION_MS);
-            anim.setAnimationListener(new AnimationListener() {
-                public void onAnimationEnd(Animation animation) {
-                }
-                public void onAnimationRepeat(Animation animation) {
-                }
-                public void onAnimationStart(Animation animation) {
-                    removeListeners();
-                }
-            });
-            setAnimation(anim);
-        } else {
-            removeListeners();
-            setAnimation(null);
-        }
-        setVisibility(View.GONE);
-        shouldSetVisibleOnUpdate = false;
-    }
-
-    private void updateBufferSize() {
-        int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
-        int capacity = viewWidth * viewHeight * pixelSize;
-        if (buffer == null || buffer.capacity() != capacity) {
-            buffer = DirectBufferAllocator.free(buffer);
-            buffer = DirectBufferAllocator.allocate(capacity);
-        }
-    }
-
-    private boolean isRendering() {
-        return (startTimeReRender != 0);
-    }
-
-    private boolean renderFrequencyTooHigh() {
-        return ((System.nanoTime() - lastStartTimeReRender) < MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS);
-    }
-
-    @WrapForJNI(dispatchTo = "gecko_priority")
-    private static native void requestZoomedViewData(ByteBuffer buffer, int tabId,
-                                                     int xPos, int yPos, int width,
-                                                     int height, float scale);
-
-    @Override
-    public void requestZoomedViewRender() {
-        if (stopUpdateView) {
-            return;
-        }
-        // remove pending runnable
-        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-
-        // "requestZoomedViewRender" can be called very often by Gecko (endDrawing in LayerRender) without
-        // any thing changed in the zoomed area (useless calls from the "zoomed area" point of view).
-        // "requestZoomedViewRender" can take time to re-render the zoomed view, it depends of the complexity
-        // of the html on this area.
-        // To avoid to slow down the application, the 2 following cases are tested:
-
-        // 1- Last render is still running, plan another render later.
-        if (isRendering()) {
-            // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
-            // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
-            // For a static html page WITHOUT any animation/video, there is a last call to endDrawing and we need to make
-            // the zoomed render on this last call.
-            ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
-            return;
-        }
-
-        // 2- Current render occurs too early, plan another render later.
-        if (renderFrequencyTooHigh()) {
-            // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
-            // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
-            // For a page WITH animation/video, the animation/video can be stopped, and we need to make
-            // the zoomed render on this last call.
-            ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
-            return;
-        }
-
-        startTimeReRender = System.nanoTime();
-        // Allocate the buffer if it's the first call.
-        // Change the buffer size if it's not the right size.
-        updateBufferSize();
-
-        int tabId = Tabs.getInstance().getSelectedTab().getId();
-
-        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-        PointF origin = metrics.getOrigin();
-
-        final int xPos = (int)origin.x + lastPosition.x;
-        final int yPos = (int)origin.y + lastPosition.y;
-
-        requestZoomedViewData(buffer, tabId, xPos, yPos, viewWidth, viewHeight,
-                              zoomFactor * metrics.zoomFactor);
-    }
-
-}
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
@@ -143,17 +143,16 @@ public class GeckoPreferences
     private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata";
     private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more";
     private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
     private static final String PREFS_DEVTOOLS_REMOTE_USB_ENABLED = "devtools.remote.usb.enabled";
     private static final String PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED = "devtools.remote.wifi.enabled";
     private static final String PREFS_DEVTOOLS_REMOTE_LINK = NON_PREF_PREFIX + "remote_debugging.link";
     private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.state";
     private static final String PREFS_TRACKING_PROTECTION_PB = "privacy.trackingprotection.pbmode.enabled";
-    private static final String PREFS_ZOOMED_VIEW_ENABLED = "ui.zoomedview.enabled";
     public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
     public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
     private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
     private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
     private static final String PREFS_CLEAR_PRIVATE_DATA = NON_PREF_PREFIX + "privacy.clear";
     private static final String PREFS_CLEAR_PRIVATE_DATA_EXIT = NON_PREF_PREFIX + "history.clear_on_exit";
     private static final String PREFS_SCREEN_ADVANCED = NON_PREF_PREFIX + "advanced_screen";
     public static final String PREFS_HOMEPAGE = NON_PREF_PREFIX + "homepage";
@@ -835,22 +834,16 @@ public class GeckoPreferences
                 } else if (PREFS_TAB_QUEUE.equals(key)) {
                     tabQueuePreference = (SwitchPreference) pref;
                     // Only show tab queue pref on nightly builds with the tab queue build flag.
                     if (!TabQueueHelper.TAB_QUEUE_ENABLED) {
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
-                } else if (PREFS_ZOOMED_VIEW_ENABLED.equals(key)) {
-                    if (!AppConstants.NIGHTLY_BUILD) {
-                        preferences.removePreference(pref);
-                        i--;
-                        continue;
-                    }
                 } else if (PREFS_VOICE_INPUT_ENABLED.equals(key)) {
                     if (!InputOptionsUtils.supportsVoiceRecognizer(getApplicationContext(), getResources().getString(R.string.voicesearch_prompt))) {
                         // Remove UI for voice input on non nightly builds.
                         preferences.removePreference(pref);
                         i--;
                         continue;
                     }
                 } else if (PREFS_QRCODE_ENABLED.equals(key)) {
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
@@ -166,19 +166,20 @@ public class FloatingToolbarTextSelectio
     }
 
     private void updateRect(final GeckoBundle message) {
         final double x = message.getDouble("x");
         final double y = (int) message.getDouble("y");
         final double width = (int) message.getDouble("width");
         final double height = (int) message.getDouble("height");
 
+        final float toolbarOffset = layerView.getCurrentToolbarHeight();
         final float zoomFactor = layerView.getZoomFactor();
         layerView.getLocationInWindow(locationInWindow);
 
         contentRect = new Rect(
                 (int) (x * zoomFactor + locationInWindow[0]),
-                (int) (y * zoomFactor + locationInWindow[1]),
+                (int) (y * zoomFactor + locationInWindow[1] + toolbarOffset),
                 (int) ((x + width) * zoomFactor + locationInWindow[0]),
                 (int) ((y + height) * zoomFactor + locationInWindow[1] +
                        (height > 0 ? handlesOffset : 0)));
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.AppConstants.Versions;
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Shader;
 
 class CanvasDelegate {
     Paint mPaint;
@@ -29,34 +30,29 @@ class CanvasDelegate {
 
         // DST_IN masks, DST_OUT clips.
         mMode = new PorterDuffXfermode(mode);
 
         mPaint = paint;
     }
 
     void draw(Canvas canvas, Path path, int width, int height) {
-        // Save the canvas. All PorterDuff operations should be done in a offscreen bitmap.
-        int count = canvas.saveLayer(0, 0, width, height, null,
-                                     Canvas.MATRIX_SAVE_FLAG |
-                                     Canvas.CLIP_SAVE_FLAG |
-                                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
-                                     Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
-                                     Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+        Bitmap offscreen = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas offscreenCanvas = new Canvas(offscreen);
 
         // Do a default draw.
-        mDrawManager.defaultDraw(canvas);
+        mDrawManager.defaultDraw(offscreenCanvas);
 
         if (path != null && !path.isEmpty()) {
             // ICS added double-buffering, which made it easier for drawing the Path directly over the DST.
             // In pre-ICS, drawPath() doesn't seem to use ARGB_8888 mode for performance, hence transparency is not preserved.
             mPaint.setXfermode(mMode);
-            canvas.drawPath(path, mPaint);
+            offscreenCanvas.drawPath(path, mPaint);
         }
 
-        // Restore the canvas.
-        canvas.restoreToCount(count);
+        offscreen.prepareToDraw();
+        canvas.drawBitmap(offscreen, 0, 0, null);
     }
 
     void setShader(Shader shader) {
         mPaint.setShader(shader);
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java
@@ -37,18 +37,17 @@ public class PhoneTabsButton extends Sha
 
         Point[] nodes = getDirectionalNodes(width, height, layoutDirection);
         TabCurve.Direction directionalCurve = getDirectionalCurve(layoutDirection);
 
         mPath.reset();
 
         mPath.moveTo(nodes[0].x, nodes[0].y);
         TabCurve.drawFromTop(mPath, nodes[1].x, nodes[1].y, directionalCurve);
-        mPath.lineTo(nodes[2].x, nodes[2].y);
-        mPath.lineTo(nodes[3].x, nodes[3].y);
+        mPath.lineTo(nodes[1].x, nodes[1].y);
         mPath.lineTo(nodes[0].x, nodes[0].y);
     }
 
     private static TabCurve.Direction getDirectionalCurve(int direction) {
         if (direction == ViewCompat.LAYOUT_DIRECTION_RTL) {
             //  right to LEFT
             return TabCurve.Direction.LEFT;
         } else {
@@ -58,23 +57,20 @@ public class PhoneTabsButton extends Sha
     }
 
     private static Point[] getDirectionalNodes(int width, int height, int layoutDirection) {
         final Point[] nodes;
         if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
             nodes = new Point[] {
                     new Point(width, 0)
                     , new Point(width, height)
-                    , new Point(0, height)
-                    , new Point(0, 0)
             };
         } else {
             nodes = new Point[]{
                     new Point(0, 0)
                     , new Point(0, height)
-                    , new Point(width, height)
-                    , new Point(width, 0)
             };
+
         }
         return nodes;
     }
 
 }
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java
@@ -35,17 +35,17 @@ public class ShapedButton extends Themed
 
         // Path is clipped.
         mPath = new Path();
 
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
         paint.setColor(ContextCompat.getColor(context, R.color.canvas_delegate_paint));
         paint.setStrokeWidth(0.0f);
-        mCanvasDelegate = new CanvasDelegate(this, Mode.DST_IN, paint);
+        mCanvasDelegate = new CanvasDelegate(this, Mode.DST_OUT, paint);
 
         setWillNotDraw(false);
     }
 
     @Override
     @SuppressLint("MissingSuperCall") // Super gets called from defaultDraw().
                                       // It is intentionally not called in the other case.
     public void draw(Canvas canvas) {
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -518,21 +518,16 @@
 <!ENTITY doorhanger_login_edit_username_hint "Username">
 <!ENTITY doorhanger_login_edit_password_hint "Password">
 <!ENTITY doorhanger_login_edit_toggle "Show password">
 <!ENTITY doorhanger_login_select_message "Copy password from &formatS;?">
 <!ENTITY doorhanger_login_select_toast_copy "Password copied to clipboard">
 <!ENTITY doorhanger_login_select_action_text "Select another login">
 <!ENTITY doorhanger_login_select_title "Copy password from">
 
-<!-- Localization note (pref_prevent_magnifying_glass): Label for setting that controls
-     whether or not the magnifying glass is disabled. -->
-<!ENTITY pref_magnifying_glass_enabled "Magnify small areas">
-<!ENTITY pref_magnifying_glass_enabled_summary2 "Enlarge links and form fields when touching near them">
-
 <!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
      whether or not the dynamic toolbar is enabled. -->
 <!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
 <!ENTITY pref_scroll_title_bar_summary2 "Hide the &brandShortName; toolbar when scrolling down a page">
 
 <!ENTITY pref_tab_queue_title3 "Tab queue">
 <!ENTITY pref_tab_queue_summary4 "Save links until the next time you open &brandShortName;">
 
@@ -732,24 +727,16 @@ just addresses the organization to follo
 <!-- Miscellaneous -->
 <!-- LOCALIZATION NOTE (ellipsis): This text is appended to a piece of text that does not fit in the
      designated space. Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit
      traditions in your locale. -->
 <!ENTITY ellipsis "…">
 
 <!ENTITY colon ":">
 
-<!-- LOCALIZATION NOTE (percent): The percent sign is appended after a number to
-     display a percentage value. formatS is the number, #37 is the code to display a percent sign.
-     This format string is typically used by getString method, in such method the percent sign
-     is a reserved caracter. In order to display one percent sign in the result of getString,
-     double percent signs must be inserted in the format string.
-     This entity is used in the zoomed view to display the zoom factor-->
-<!ENTITY percent "&formatS;&#37;&#37;">
-
 <!-- These are only used for accessibility for the done and overflow-menu buttons in the actionbar.
      They are never shown to users -->
 <!ENTITY actionbar_menu "Menu">
 <!ENTITY actionbar_done "Done">
 
 <!-- Voice search in the awesome bar -->
 <!ENTITY voicesearch_prompt "Speak now">
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -375,24 +375,22 @@ gvjar.sources += [geckoview_source_dir +
     'gfx/BufferedImage.java',
     'gfx/BufferedImageGLInfo.java',
     'gfx/DynamicToolbarAnimator.java',
     'gfx/FloatSize.java',
     'gfx/FullScreenState.java',
     'gfx/GeckoLayerClient.java',
     'gfx/ImmutableViewportMetrics.java',
     'gfx/IntSize.java',
-    'gfx/LayerRenderer.java',
     'gfx/LayerView.java',
     'gfx/NativePanZoomController.java',
     'gfx/Overscroll.java',
     'gfx/OverscrollEdgeEffect.java',
     'gfx/PanningPerfAPI.java',
     'gfx/PanZoomController.java',
-    'gfx/PanZoomTarget.java',
     'gfx/PointUtils.java',
     'gfx/RectUtils.java',
     'gfx/RenderTask.java',
     'gfx/StackScroller.java',
     'gfx/SurfaceTextureListener.java',
     'gfx/ViewTransform.java',
     'gfx/VsyncSource.java',
     'InputConnectionListener.java',
@@ -939,17 +937,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'widget/RoundedCornerLayout.java',
     'widget/SiteLogins.java',
     'widget/SquaredImageView.java',
     'widget/SquaredRelativeLayout.java',
     'widget/SwipeDismissListViewTouchListener.java',
     'widget/TabThumbnailWrapper.java',
     'widget/ThumbnailView.java',
     'widget/TouchDelegateWithReset.java',
-    'ZoomedView.java',
 ]]
 # The following sources are checked in to version control but
 # generated by a script (java/org/mozilla/gecko/widget/themed/generate_themed_views.py).
 # If you're editing this list, make sure to edit that script.
 gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
     'widget/themed/ThemedEditText.java',
     'widget/themed/ThemedFrameLayout.java',
     'widget/themed/ThemedImageButton.java',
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/toolbar_grey_round.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/toolbar_grey"/>
-    <corners android:radius="@dimen/standard_corner_radius"/>
-</shape>
-
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -42,22 +42,16 @@
                                                android:layout_height="match_parent"
                                                android:visibility="gone"/>
 
             <view class="org.mozilla.gecko.media.VideoPlayer" android:id="@+id/video_player"
                          android:layout_height="match_parent"
                          android:layout_width="match_parent">
             </view>
 
-            <ViewStub android:id="@+id/zoomed_view_stub"
-                      android:inflatedId="@+id/zoomed_view"
-                      android:layout="@layout/zoomed_view"
-                      android:layout_width="wrap_content"
-                      android:layout_height="wrap_content" />
-
             <FrameLayout android:id="@+id/home_screen_container"
                          android:layout_width="match_parent"
                          android:layout_height="match_parent"
                          android:visibility="gone">
 
                 <ViewStub android:id="@+id/home_pager_stub"
                           android:layout="@layout/home_pager"
                           android:layout_width="match_parent"
deleted file mode 100644
--- a/mobile/android/base/resources/layout/zoomed_view.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   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/.
--->
-
-<org.mozilla.gecko.ZoomedView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:gecko="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/zoomed_view_container"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="@drawable/dropshadow"
-    android:padding="@dimen/drawable_dropshadow_size"
-    android:visibility="gone">
-
-    <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentTop="true"
-        android:background="@drawable/toolbar_grey_round">
-        <!--
-           Zoom factor button is invisible by default. Set ui.zoomedview.simplified to false
-           in order to display the button in the zoomed view tool bar
-        -->
-        <TextView
-            android:id="@+id/change_zoom_factor"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/zoomed_view_toolbar_height"
-            android:background="@android:color/transparent"
-            android:padding="12dip"
-            android:layout_alignLeft="@+id/zoomed_image_view"
-            android:textSize="16sp"
-            android:textColor="@color/text_and_tabs_tray_grey"
-            android:visibility="invisible"/>
-        <ImageView
-            android:id="@+id/dialog_close"
-            android:scaleType="center"
-            android:layout_width="@dimen/zoomed_view_toolbar_height"
-            android:layout_height="@dimen/zoomed_view_toolbar_height"
-            android:layout_alignRight="@id/zoomed_image_view"
-            android:src="@drawable/close_edit_mode_selector"/>
-        <ImageView
-            android:id="@id/zoomed_image_view"
-            android:layout_below="@id/change_zoom_factor"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
-    </RelativeLayout>
-
-</org.mozilla.gecko.ZoomedView>
\ No newline at end of file
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -196,28 +196,23 @@
     <dimen name="tab_history_combo_margin_start">15dp</dimen>
     <dimen name="tab_history_combo_margin_end">15dp</dimen>
     <dimen name="tab_history_title_fading_width">50dp</dimen>
     <dimen name="tab_history_title_margin_end">15dp</dimen>
     <dimen name="tab_history_title_text_size">14sp</dimen>
     <dimen name="tab_history_bg_width">2dp</dimen>
     <dimen name="tab_history_border_padding">2dp</dimen>
 
-    <!-- ZoomedView dimensions. -->
-    <dimen name="zoomed_view_toolbar_height">44dp</dimen>
-    <dimen name="drawable_dropshadow_size">3dp</dimen>
-
     <!-- Find-In-Page dialog dimensions. -->
     <dimen name="find_in_page_text_margin_start">5dip</dimen>
     <dimen name="find_in_page_text_margin_end">12dip</dimen>
     <dimen name="find_in_page_text_padding_start">10dip</dimen>
     <dimen name="find_in_page_text_padding_end">10dip</dimen>
     <dimen name="find_in_page_status_margin_end">10dip</dimen>
     <dimen name="find_in_page_control_margin_top">2dip</dimen>
-    <dimen name="progress_bar_scroll_offset">1.5dp</dimen>
 
     <!-- Matches the built-in divider height. fwiw, in the framework
          I suspect this is a drawable rather than a dimen.  -->
     <dimen name="action_bar_divider_height">2dp</dimen>
 
     <!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
     <item name="match_parent" type="dimen">-1</item>
     <item name="wrap_content" type="dimen">-2</item>
--- a/mobile/android/base/resources/xml/preferences_accessibility.xml
+++ b/mobile/android/base/resources/xml/preferences_accessibility.xml
@@ -10,20 +10,16 @@
     <SwitchPreference android:key="android.not_a_preference.font.size.use_system_font_size"
                       android:title="@string/pref_use_system_font_size"
                       android:summary="@string/pref_use_system_font_size_summary" />
 
     <SwitchPreference android:key="browser.ui.zoom.force-user-scalable"
                       android:title="@string/pref_zoom_force_enabled"
                       android:summary="@string/pref_zoom_force_enabled_summary" />
 
-    <SwitchPreference android:key="ui.zoomedview.enabled"
-                      android:title="@string/pref_magnifying_glass_enabled"
-                      android:summary="@string/pref_magnifying_glass_enabled_summary" />
-
     <SwitchPreference android:key="android.not_a_preference.voice_input_enabled"
                       android:title="@string/pref_voice_input"
                       android:summary="@string/pref_voice_input_summary"
                       android:defaultValue="true"/>
 
     <SwitchPreference android:key="android.not_a_preference.qrcode_enabled"
                       android:title="@string/pref_qrcode_enabled"
                       android:summary="@string/pref_qrcode_enabled_summary"
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -416,19 +416,16 @@
   <string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
   <string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
   <string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
   <string name="doorhanger_login_select_message">&doorhanger_login_select_message;</string>
   <string name="doorhanger_login_select_toast_copy">&doorhanger_login_select_toast_copy;</string>
   <string name="doorhanger_login_select_action_text">&doorhanger_login_select_action_text;</string>
   <string name="doorhanger_login_select_title">&doorhanger_login_select_title;</string>
 
-  <string name="pref_magnifying_glass_enabled">&pref_magnifying_glass_enabled;</string>
-  <string name="pref_magnifying_glass_enabled_summary">&pref_magnifying_glass_enabled_summary2;</string>
-
   <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
   <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary2;</string>
 
   <string name="pref_compact_tabs">&pref_compact_tabs;</string>
   <string name="pref_compact_tabs_summary">&pref_compact_tabs_summary2;</string>
 
   <string name="page_removed">&page_removed;</string>
 
@@ -596,18 +593,16 @@
   <string name="restrictable_feature_block_list">&restrictable_feature_block_list;</string>
   <string name="restrictable_feature_block_list_description">&restrictable_feature_block_list_description;</string>
 
   <!-- Miscellaneous -->
   <string name="ellipsis">&ellipsis;</string>
 
   <string name="colon">&colon;</string>
 
-  <string name="percent">&percent;</string>
-
   <string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
 
   <string name="intent_uri_private_browsing_prompt">&intent_uri_private_browsing_prompt;</string>
   <string name="intent_uri_private_browsing_multiple_match_title">&intent_uri_private_browsing_multiple_match_title;</string>
 
   <string name="devtools_auth_scan_header">&devtools_auth_scan_header;</string>
 
   <string name="unsupported_sdk_version">&unsupported_sdk_version;</string>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -383,18 +383,16 @@ var BrowserApp = {
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
     dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
     Services.obs.notifyObservers(this.browser, "BrowserChrome:Ready");
 
     this.deck = document.getElementById("browsers");
 
     BrowserEventHandler.init();
 
-    ViewportHandler.init();
-
     Services.androidBridge.browserApp = this;
 
     GlobalEventDispatcher.registerListener(this, [
       "Tab:Load",
       "Tab:Selected",
       "Tab:Closed",
       "Tab:Move",
       "Browser:LoadManifest",
@@ -554,17 +552,16 @@ var BrowserApp = {
       if (!AppConstants.RELEASE_OR_BETA) {
         InitLater(() => WebcompatReporter.init());
       }
 
       // Collect telemetry data.
       // We do this at startup because we want to move away from "gather-telemetry" (bug 1127907)
       InitLater(() => {
         Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState()));
-        Telemetry.addData("ZOOMED_VIEW_ENABLED", Services.prefs.getBoolPref("ui.zoomedview.enabled"));
       });
 
       InitLater(() => LightWeightThemeWebInstaller.init());
       InitLater(() => CastingApps.init(), window, "CastingApps");
       InitLater(() => Services.search.init(), Services, "search");
       InitLater(() => DownloadNotifications.init(), window, "DownloadNotifications");
 
       // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
@@ -4506,18 +4503,16 @@ Tab.prototype = {
       sameDocument: sameDocument,
 
       canGoBack: webNav.canGoBack,
       canGoForward: webNav.canGoForward,
     };
 
     GlobalEventDispatcher.sendRequest(message);
 
-    BrowserEventHandler.closeZoomedView();
-
     notifyManifestStatus(this.browser);
 
     if (!sameDocument) {
       // XXX This code assumes that this is the earliest hook we have at which
       // browser.contentDocument is changed to the new document we're loading
       this.contentDocumentIsDisplayed = false;
       this.hasTouchListener = false;
       Services.obs.notifyObservers(this.browser, "Session:NotifyLocationChange");
@@ -4727,20 +4722,16 @@ Tab.prototype = {
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
     Ci.nsIBrowserTab
   ])
 };
 
 var BrowserEventHandler = {
   init: function init() {
-    this._clickInZoomedView = false;
-    Services.obs.addObserver(this, "Gesture:SingleTap");
-    Services.obs.addObserver(this, "Gesture:ClickInZoomedView");
-
     BrowserApp.deck.addEventListener("touchend", this, true);
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport);
     BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
     BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true);
 
     InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
     InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
@@ -4808,83 +4799,16 @@ var BrowserEventHandler = {
         (aElement instanceof Ci.nsIDOMHTMLAreaElement && aElement.href))) {
       try {
         return Services.io.newURI(aElement.href);
       } catch (e) {}
     }
     return null;
   },
 
-  observe: function(aSubject, aTopic, aData) {
-    // the remaining events are all dependent on the browser content document being the
-    // same as the browser displayed document. if they are not the same, we should ignore
-    // the event.
-    if (BrowserApp.isBrowserContentDocumentDisplayed()) {
-      this.handleUserEvent(aTopic, aData);
-    }
-  },
-
-  handleUserEvent: function(aTopic, aData) {
-    switch (aTopic) {
-
-      case "Gesture:ClickInZoomedView":
-        this._clickInZoomedView = true;
-        break;
-
-      case "Gesture:SingleTap": {
-        let focusedElement = BrowserApp.getFocusedInput(BrowserApp.selectedBrowser);
-        let data = JSON.parse(aData);
-        let {x, y} = data;
-
-        if (this._inCluster && this._clickInZoomedView != true) {
-          // If there is a focused element, the display of the zoomed view won't remove the focus.
-          // In this case, the form assistant linked to the focused element will never be closed.
-          // To avoid this situation, the focus is moved and the form assistant is closed.
-          if (focusedElement) {
-            try {
-              Services.focus.moveFocus(BrowserApp.selectedBrowser.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
-            } catch(e) {
-              Cu.reportError(e);
-            }
-            WindowEventDispatcher.sendRequest({ type: "FormAssist:Hide" });
-          }
-          this._clusterClicked(x, y);
-        } else {
-          if (this._clickInZoomedView != true) {
-            this.closeZoomedView(/* animate */ true);
-          }
-        }
-        this._clickInZoomedView = false;
-        this._cancelTapHighlight();
-        break;
-      }
-
-      default:
-        dump('BrowserEventHandler.handleUserEvent: unexpected topic "' + aTopic + '"');
-        break;
-    }
-  },
-
-  closeZoomedView: function(aAnimate) {
-    WindowEventDispatcher.sendRequest({
-      type: "Gesture:CloseZoomedView",
-      animate: !!aAnimate,
-    });
-  },
-
-  _clusterClicked: function(aX, aY) {
-    WindowEventDispatcher.sendRequest({
-      type: "Gesture:ClusteredLinksClicked",
-      clickPosition: {
-        x: aX,
-        y: aY
-      }
-    });
-  },
-
   _highlightElement: null,
 
   _doTapHighlight: function _doTapHighlight(aElement) {
     this._highlightElement = aElement;
   },
 
   _cancelTapHighlight: function _cancelTapHighlight() {
     if (!this._highlightElement)
@@ -5649,29 +5573,16 @@ var XPInstallObserver = {
     NativeWindow.doorhanger.show(message, "addon-app-restart", buttons, BrowserApp.selectedTab.id, { persistence: -1 });
   },
 
   hideRestartPrompt: function() {
     NativeWindow.doorhanger.hide("addon-app-restart", BrowserApp.selectedTab.id);
   }
 };
 
-var ViewportHandler = {
-  init: function init() {
-    GlobalEventDispatcher.registerListener(this, "Window:Resize");
-  },
-
-  onEvent: function (event, data, callback) {
-    if (event == "Window:Resize" && data) {
-      let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-      windowUtils.setNextPaintSyncId(data.id);
-    }
-  }
-};
-
 /**
  * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
  */
 var PopupBlockerObserver = {
   onUpdatePageReport: function onUpdatePageReport(aEvent) {
     let browser = BrowserApp.selectedBrowser;
     if (aEvent.originalTarget != browser)
       return;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoInputConnection.java
@@ -359,17 +359,17 @@ class GeckoInputConnection
         if (v == null) {
             return;
         }
 
         int[] viewCoords = new int[2];
         v.getLocationOnScreen(viewCoords);
 
         DynamicToolbarAnimator animator = GeckoAppShell.getLayerView().getDynamicToolbarAnimator();
-        float toolbarHeight = animator.getMaxTranslation() - animator.getToolbarTranslation();
+        float toolbarHeight = (float)animator.getCurrentToolbarHeight();
 
         Matrix matrix = GeckoAppShell.getLayerView().getMatrixForLayerRectToViewRect();
         if (matrix == null) {
             if (DEBUG) {
                 Log.d(LOGTAG, "Cannot get Matrix to convert from Gecko coords to layer view coords");
             }
             return;
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -1,593 +1,185 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.gfx;
 
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.graphics.Bitmap;
 import android.graphics.PointF;
-import android.support.v4.view.ViewCompat;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.animation.LinearInterpolator;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
 public class DynamicToolbarAnimator {
     private static final String LOGTAG = "GeckoDynamicToolbarAnimator";
-    private static final String PREF_SCROLL_TOOLBAR_THRESHOLD = "browser.ui.scroll-toolbar-threshold";
 
     public static enum PinReason {
-        RELAYOUT,
-        ACTION_MODE,
-        FULL_SCREEN,
-        CARET_DRAG,
-        PAGE_LOADING
+        DISABLED(0),
+        RELAYOUT(1),
+        ACTION_MODE(2),
+        FULL_SCREEN(3),
+        CARET_DRAG(4),
+        PAGE_LOADING(5);
+
+        public final int mValue;
+        PinReason(final int value) {
+            mValue = value;
+        }
+    }
+
+    public interface MetricsListener {
+        public void onMetricsChanged(ImmutableViewportMetrics viewport);
+    }
+
+    public interface ToolbarChromeProxy {
+        public Bitmap getBitmapOfToolbarChrome();
+        public boolean isToolbarChromeVisible();
+        public void toggleToolbarChrome(boolean aShow);
     }
 
     private final Set<PinReason> pinFlags = Collections.synchronizedSet(EnumSet.noneOf(PinReason.class));
 
-    // The duration of the animation in ns
-    private static final long ANIMATION_DURATION = 150000000;
-
     private final GeckoLayerClient mTarget;
-    private final List<LayerView.DynamicToolbarListener> mListeners;
-
-    /* The translation to be applied to the toolbar UI view. This is the
-     * distance from the default/initial location (at the top of the screen,
-     * visible to the user) to where we want it to be. This variable should
-     * always be between 0 (toolbar fully visible) and the height of the toolbar
-     * (toolbar fully hidden), inclusive.
-     */
-    private float mToolbarTranslation;
-
-    /* The translation to be applied to the LayerView. This is the distance from
-     * the default/initial location (just below the toolbar, with the bottom
-     * extending past the bottom of the screen) to where we want it to be.
-     * This variable should always be between 0 and the height of the toolbar,
-     * inclusive.
-     */
-    private float mLayerViewTranslation;
-
-    /* This stores the maximum translation that can be applied to the toolbar
-     * and layerview when scrolling. This is populated with the height of the
-     * toolbar. */
-    private float mMaxTranslation;
-
-    /* This interpolator is used for the above mentioned animation */
-    private LinearInterpolator mInterpolator;
-
-    /* This is the proportion of the viewport rect that needs to be travelled
-     * while scrolling before the translation will start taking effect.
-     */
-    private float SCROLL_TOOLBAR_THRESHOLD = 0.20f;
-    /* The ID of the prefs listener for the scroll-toolbar threshold */
-    private final PrefsHelper.PrefHandler mPrefObserver;
-
-    /* While we are resizing the viewport to account for the toolbar, the Java
-     * code and painted layer metrics in the compositor have different notions
-     * of the CSS viewport height. The Java value is stored in the
-     * GeckoLayerClient's viewport metrics, and the Gecko one is stored here.
-     * This allows us to adjust fixed-pos items correctly.
-     * You must synchronize on mTarget.getLock() to read/write this. */
-    private Integer mHeightDuringResize;
-
-    /* This tracks if we should trigger a "snap" on the next composite. A "snap"
-     * is when we simultaneously move the LayerView and change the scroll offset
-     * in the compositor so that everything looks the same on the screen but
-     * has really been shifted.
-     * You must synchronize on |this| to read/write this. */
-    private boolean mSnapRequired = false;
-
-    /* The task that handles showing/hiding toolbar */
-    private DynamicToolbarAnimationTask mAnimationTask;
-
-    /* The start point of a drag, used for scroll-based dynamic toolbar
-     * behaviour. */
-    private PointF mTouchStart;
-    private float mLastTouch;
-
-    /* Set to true when root content is being scrolled */
-    private boolean mScrollingRootContent;
+    private LayerView.Compositor mCompositor;
+    private final List<MetricsListener> mListeners;
+    private ToolbarChromeProxy mToolbarChromeProxy;
+    private int mMaxToolbarHeight;
+    private boolean mCompositorControllerOpen;
 
     public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
         mTarget = aTarget;
-        mListeners = new ArrayList<LayerView.DynamicToolbarListener>();
-
-        mInterpolator = new LinearInterpolator();
-
-        // Listen to the dynamic toolbar pref
-        mPrefObserver = new PrefsHelper.PrefHandlerBase() {
-            @Override
-            public void prefValue(String pref, int value) {
-                SCROLL_TOOLBAR_THRESHOLD = value / 100.0f;
-            }
-        };
-        PrefsHelper.addObserver(new String[] { PREF_SCROLL_TOOLBAR_THRESHOLD }, mPrefObserver);
+        mListeners = new ArrayList<MetricsListener>();
     }
 
-    public void destroy() {
-        PrefsHelper.removeObserver(mPrefObserver);
-    }
-
-    public void addTranslationListener(LayerView.DynamicToolbarListener aListener) {
+    public void addMetricsListener(MetricsListener aListener) {
         mListeners.add(aListener);
     }
 
-    public void removeTranslationListener(LayerView.DynamicToolbarListener aListener) {
+    public void removeMetricsListener(MetricsListener aListener) {
         mListeners.remove(aListener);
     }
 
-    private void fireListeners() {
-        for (LayerView.DynamicToolbarListener listener : mListeners) {
-            listener.onTranslationChanged(mToolbarTranslation, mLayerViewTranslation);
+    public void setToolbarChromeProxy(ToolbarChromeProxy aToolbarChromeProxy) {
+        mToolbarChromeProxy = aToolbarChromeProxy;
+    }
+
+    /* package-private */ void onToggleChrome(boolean aShow) {
+        if (mToolbarChromeProxy != null) {
+            mToolbarChromeProxy.toggleToolbarChrome(aShow);
         }
     }
 
-    void onPanZoomStopped() {
-        for (LayerView.DynamicToolbarListener listener : mListeners) {
-            listener.onPanZoomStopped();
-        }
-    }
-
-    void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
-        for (LayerView.DynamicToolbarListener listener : mListeners) {
+    /* package-private */ void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
+        for (MetricsListener listener : mListeners) {
             listener.onMetricsChanged(aMetrics);
         }
     }
 
-    public void setMaxTranslation(float maxTranslation) {
+    public void setMaxToolbarHeight(int maxToolbarHeight) {
         ThreadUtils.assertOnUiThread();
-        if (maxTranslation < 0) {
-            Log.e(LOGTAG, "Got a negative max-translation value: " + maxTranslation + "; clamping to zero");
-            mMaxTranslation = 0;
-        } else {
-            mMaxTranslation = maxTranslation;
+        mMaxToolbarHeight = maxToolbarHeight;
+        if (mCompositor != null) {
+            mCompositor.setMaxToolbarHeight(mMaxToolbarHeight);
         }
     }
 
-    public float getMaxTranslation() {
-        return mMaxTranslation;
-    }
-
-    public float getToolbarTranslation() {
-        return mToolbarTranslation;
+    public int getCurrentToolbarHeight() {
+        if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
+            return mMaxToolbarHeight;
+        }
+        return 0;
     }
 
     /**
      * If true, scroll changes will not affect translation.
      */
     public boolean isPinned() {
         return !pinFlags.isEmpty();
     }
 
     public boolean isPinnedBy(PinReason reason) {
         return pinFlags.contains(reason);
     }
 
     public void setPinned(boolean pinned, PinReason reason) {
+        if ((mCompositor != null) && (pinned != pinFlags.contains(reason))) {
+             mCompositor.setPinned(pinned, reason.mValue);
+        }
+
         if (pinned) {
             pinFlags.add(reason);
         } else {
             pinFlags.remove(reason);
         }
     }
 
     public void showToolbar(boolean immediately) {
-        animateToolbar(true, immediately);
+        if (mCompositor != null) {
+            mCompositor.sendToolbarAnimatorMessage(immediately ?
+                LayerView.REQUEST_SHOW_TOOLBAR_IMMEDIATELY : LayerView.REQUEST_SHOW_TOOLBAR_ANIMATED);
+        }
     }
 
     public void hideToolbar(boolean immediately) {
-        animateToolbar(false, immediately);
-    }
-
-    public void setScrollingRootContent(boolean isRootContent) {
-        mScrollingRootContent = isRootContent;
-    }
-
-    private void animateToolbar(final boolean showToolbar, boolean immediately) {
-        ThreadUtils.assertOnUiThread();
-
-        if (mAnimationTask != null) {
-            mTarget.getView().removeRenderTask(mAnimationTask);
-            mAnimationTask = null;
-        }
-
-        float desiredTranslation = (showToolbar ? 0 : mMaxTranslation);
-        Log.v(LOGTAG, "Requested " + (immediately ? "immediate " : "") + "toolbar animation to translation " + desiredTranslation);
-        if (FloatUtils.fuzzyEquals(mToolbarTranslation, desiredTranslation)) {
-            // If we're already pretty much in the desired position, don't bother
-            // with a full animation; do an immediate jump
-            immediately = true;
-            Log.v(LOGTAG, "Changing animation to immediate jump");
-        }
-
-        if (showToolbar && immediately) {
-            // Special case for showing the toolbar immediately: some of the call
-            // sites expect this to happen synchronously, so let's do that. This
-            // is safe because if we are showing the toolbar from a hidden state
-            // there is no chance of showing garbage
-            mToolbarTranslation = desiredTranslation;
-            fireListeners();
-            // And then proceed with the normal flow (some of which will be
-            // a no-op now)...
-        }
-
-        if (!showToolbar) {
-            // If we are hiding the toolbar, we need to move the LayerView first,
-            // so that we don't end up showing garbage under the toolbar when
-            // it is hidden. In the case that we are showing the toolbar, we
-            // move the LayerView after the toolbar is shown - the
-            // DynamicToolbarAnimationTask calls that upon completion.
-            shiftLayerView(desiredTranslation);
-        }
-
-        mAnimationTask = new DynamicToolbarAnimationTask(desiredTranslation, immediately, showToolbar);
-        mTarget.getView().postRenderTask(mAnimationTask);
-    }
-
-    private synchronized void shiftLayerView(float desiredTranslation) {
-        float layerViewTranslationNeeded = desiredTranslation - mLayerViewTranslation;
-        mLayerViewTranslation = desiredTranslation;
-        synchronized (mTarget.getLock()) {
-            if (layerViewTranslationNeeded == 0 && isResizing()) {
-                // We're already in the middle of a snap, so this new call is
-                // redundant as it's snapping to the same place. Ignore it.
-                return;
-            }
-            mHeightDuringResize = new Integer(mTarget.getViewportMetrics().viewportRectHeight);
-            mSnapRequired = mTarget.setViewportSize(
-                mTarget.getView().getWidth(),
-                mTarget.getView().getHeight() - Math.round(mMaxTranslation - mLayerViewTranslation),
-                new PointF(0, -layerViewTranslationNeeded));
-            if (!mSnapRequired) {
-                mHeightDuringResize = null;
-                ThreadUtils.postToUiThread(new Runnable() {
-                    // Post to run it outside of the synchronize blocks. The
-                    // delay shouldn't hurt.
-                    @Override
-                    public void run() {
-                        fireListeners();
-                    }
-                });
-            }
-            // Request a composite, which will trigger the snap.
-            mTarget.getView().requestRender();
+        if (mCompositor != null) {
+            mCompositor.sendToolbarAnimatorMessage(immediately ?
+                LayerView.REQUEST_HIDE_TOOLBAR_IMMEDIATELY : LayerView.REQUEST_HIDE_TOOLBAR_ANIMATED);
         }
     }
 
-    IntSize getViewportSize() {
+    /* package-private */ IntSize getViewportSize() {
         ThreadUtils.assertOnUiThread();
 
         int viewWidth = mTarget.getView().getWidth();
         int viewHeight = mTarget.getView().getHeight();
-        float toolbarTranslation = mToolbarTranslation;
-        if (mAnimationTask != null) {
-            // If we have an animation going, mToolbarTranslation may be in flux
-            // and we should use the final value it will settle on.
-            toolbarTranslation = mAnimationTask.getFinalToolbarTranslation();
-        }
-        int viewHeightVisible = viewHeight - Math.round(mMaxTranslation - toolbarTranslation);
-        return new IntSize(viewWidth, viewHeightVisible);
-    }
-
-    boolean isResizing() {
-        return mHeightDuringResize != null;
-    }
-
-    private final Runnable mSnapRunnable = new Runnable() {
-        private int mFrame = 0;
-
-        @Override
-        public final void run() {
-            // It takes 2 frames for the view translation to take effect, at
-            // least on a Nexus 4 device running Android 4.2.2. So we wait for
-            // two frames before doing the notifyAll(), otherwise we get a
-            // short user-visible glitch.
-            // TODO: find a better way to do this, if possible.
-            if (mFrame == 1) {
-                synchronized (this) {
-                    this.notifyAll();
-                }
-                mFrame = 0;
-                return;
-            }
-
-            if (mFrame == 0) {
-                fireListeners();
-            }
-
-            ViewCompat.postOnAnimation(mTarget.getView(), this);
-            mFrame++;
-        }
-    };
-
-    void scrollChangeResizeCompleted() {
-        synchronized (mTarget.getLock()) {
-            Log.v(LOGTAG, "Scrollchange resize completed");
-            mHeightDuringResize = null;
-        }
-    }
-
-    /**
-     * "Shrinks" the absolute value of aValue by moving it closer to zero by
-     * aShrinkAmount, but prevents it from crossing over zero. If aShrinkAmount
-     * is negative it is ignored.
-     * @return The shrunken value.
-     */
-    private static float shrinkAbs(float aValue, float aShrinkAmount) {
-        if (aShrinkAmount <= 0) {
-            return aValue;
-        }
-        float shrinkBy = Math.min(Math.abs(aValue), aShrinkAmount);
-        return (aValue < 0 ? aValue + shrinkBy : aValue - shrinkBy);
-    }
-
-    /**
-     * This function takes in a scroll amount and decides how much of that
-     * should be used up to translate things on screen because of the dynamic
-     * toolbar behaviour. It returns the maximum amount that could be used
-     * for translation purposes; the rest must be used for scrolling.
-     */
-    private float decideTranslation(float aDelta,
-                                    ImmutableViewportMetrics aMetrics,
-                                    float aTouchTravelDistance) {
-
-        float exposeThreshold = aMetrics.getHeight() * SCROLL_TOOLBAR_THRESHOLD;
-        float translation = aDelta;
-
-        if (translation < 0) { // finger moving upwards
-            translation = shrinkAbs(translation, aMetrics.getOverscroll().top);
-
-            // If the toolbar is in a state between fully hidden and fully shown
-            // (i.e. the user is actively translating it), then we want the
-            // translation to take effect right away. Or if the user has moved
-            // their finger past the required threshold (and is not trying to
-            // scroll past the bottom of the page) then also we want the touch
-            // to cause translation. If the toolbar is fully visible, we only
-            // want the toolbar to hide if the user is scrolling the root content.
-            boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
-            boolean reachedThreshold = -aTouchTravelDistance >= exposeThreshold;
-            boolean atBottomOfPage = aMetrics.viewportRectBottom() >= aMetrics.pageRectBottom;
-            if (inBetween || (mScrollingRootContent && reachedThreshold && !atBottomOfPage)) {
-                return translation;
-            }
-        } else {    // finger moving downwards
-            translation = shrinkAbs(translation, aMetrics.getOverscroll().bottom);
-
-            // Ditto above comment, but in this case if they reached the top and
-            // the toolbar is not shown, then we do want to allow translation
-            // right away.
-            boolean inBetween = (mToolbarTranslation != 0 && mToolbarTranslation != mMaxTranslation);
-            boolean reachedThreshold = aTouchTravelDistance >= exposeThreshold;
-            boolean atTopOfPage = aMetrics.viewportRectTop <= aMetrics.pageRectTop;
-            boolean isToolbarTranslated = (mToolbarTranslation != 0);
-            if (inBetween || reachedThreshold || (atTopOfPage && isToolbarTranslated)) {
-                return translation;
-            }
+        if ((mToolbarChromeProxy != null) && mToolbarChromeProxy.isToolbarChromeVisible()) {
+          viewHeight -= mMaxToolbarHeight;
         }
-
-        return 0;
-    }
-
-    // Timestamp of the start of the touch event used to calculate toolbar velocity
-    private long mLastEventTime;
-    // Current velocity of the toolbar. Used to populate the velocity queue in C++APZ.
-    private float mVelocity;
-
-    boolean onInterceptTouchEvent(MotionEvent event) {
-        if (isPinned()) {
-            return false;
-        }
-
-        // Animations should never co-exist with the user touching the screen.
-        if (mAnimationTask != null) {
-            mTarget.getView().removeRenderTask(mAnimationTask);
-            mAnimationTask = null;
-        }
-
-        // we only care about single-finger drags here; any other kind of event
-        // should reset and cause us to start over.
-        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE ||
-            event.getActionMasked() != MotionEvent.ACTION_MOVE ||
-            event.getPointerCount() != 1)
-        {
-            if (mTouchStart != null) {
-                Log.v(LOGTAG, "Resetting touch sequence due to non-move");
-                mTouchStart = null;
-                mVelocity = 0.0f;
-            }
-
-            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                // We need to do this even if the toolbar is already fully
-                // visible or fully hidden, because this is what triggers the
-                // viewport resize in content and updates the viewport metrics.
-                boolean toolbarMostlyVisible = mToolbarTranslation < (mMaxTranslation / 2);
-                Log.v(LOGTAG, "All fingers lifted, completing " + (toolbarMostlyVisible ? "show" : "hide"));
-                animateToolbar(toolbarMostlyVisible, false);
-            }
-            return false;
-        }
-
-        if (mTouchStart != null) {
-            float prevDir = mLastTouch - mTouchStart.y;
-            float newDir = event.getRawY() - mLastTouch;
-            if (prevDir != 0 && newDir != 0 && ((prevDir < 0) != (newDir < 0))) {
-                // If the direction of movement changed, reset the travel
-                // distance properties.
-                mTouchStart = null;
-                mVelocity = 0.0f;
-            }
-        }
-
-        if (mTouchStart == null) {
-            mTouchStart = new PointF(event.getRawX(), event.getRawY());
-            mLastTouch = event.getRawY();
-            mLastEventTime = event.getEventTime();
-            return false;
-        }
-
-        float deltaY = event.getRawY() - mLastTouch;
-        long currentTime = event.getEventTime();
-        float deltaTime = (float)(currentTime - mLastEventTime);
-        mLastEventTime = currentTime;
-        if (deltaTime > 0.0f) {
-            mVelocity = -deltaY / deltaTime;
-        } else {
-            mVelocity = 0.0f;
-        }
-        mLastTouch = event.getRawY();
-        float travelDistance = event.getRawY() - mTouchStart.y;
-
-        ImmutableViewportMetrics metrics = mTarget.getViewportMetrics();
-
-        if (metrics.getPageHeight() <= mTarget.getView().getHeight() &&
-            mToolbarTranslation == 0) {
-            // If the page is short and the toolbar is already visible, don't
-            // allow translating it out of view.
-            return false;
-        }
-
-        float translation = decideTranslation(deltaY, metrics, travelDistance);
-
-        float oldToolbarTranslation = mToolbarTranslation;
-        float oldLayerViewTranslation = mLayerViewTranslation;
-        mToolbarTranslation = FloatUtils.clamp(mToolbarTranslation - translation, 0, mMaxTranslation);
-        mLayerViewTranslation = FloatUtils.clamp(mLayerViewTranslation - translation, 0, mMaxTranslation);
-
-        if (oldToolbarTranslation == mToolbarTranslation &&
-            oldLayerViewTranslation == mLayerViewTranslation) {
-            return false;
-        }
-
-        if (mToolbarTranslation == mMaxTranslation) {
-            Log.v(LOGTAG, "Toolbar at maximum translation, calling shiftLayerView(" + mMaxTranslation + ")");
-            shiftLayerView(mMaxTranslation);
-        } else if (mToolbarTranslation == 0) {
-            Log.v(LOGTAG, "Toolbar at minimum translation, calling shiftLayerView(0)");
-            shiftLayerView(0);
-        }
-
-        fireListeners();
-        mTarget.getView().requestRender();
-        return true;
-    }
-
-    // Get the current velocity of the toolbar.
-    float getVelocity() {
-        return mVelocity;
+        return new IntSize(viewWidth, viewHeight);
     }
 
     public PointF getVisibleEndOfLayerView() {
         return new PointF(mTarget.getView().getWidth(),
-            mTarget.getView().getHeight() - mMaxTranslation + mLayerViewTranslation);
-    }
-
-    private float bottomOfCssViewport(ImmutableViewportMetrics aMetrics) {
-        return (isResizing() ? mHeightDuringResize : aMetrics.getHeight())
-                + mMaxTranslation - mLayerViewTranslation;
-    }
-
-    private synchronized boolean getAndClearSnapRequired() {
-        boolean snapRequired = mSnapRequired;
-        mSnapRequired = false;
-        return snapRequired;
+            mTarget.getView().getHeight());
     }
 
-    void populateViewTransform(ViewTransform aTransform, ImmutableViewportMetrics aMetrics) {
-        if (getAndClearSnapRequired()) {
-            synchronized (mSnapRunnable) {
-                ViewCompat.postOnAnimation(mTarget.getView(), mSnapRunnable);
-                try {
-                    // hold the in-progress composite until the views have been
-                    // translated because otherwise there is a visible glitch.
-                    // don't hold for more than 100ms just in case.
-                    mSnapRunnable.wait(100);
-                } catch (InterruptedException ie) {
-                }
+    private void dumpStateToCompositor() {
+        if ((mCompositor != null) && mCompositorControllerOpen) {
+            mCompositor.setMaxToolbarHeight(mMaxToolbarHeight);
+            for (PinReason reason : pinFlags) {
+              mCompositor.setPinned(true, reason.mValue);
             }
         }
-
-        aTransform.x = aMetrics.viewportRectLeft;
-        aTransform.y = aMetrics.viewportRectTop;
-        aTransform.width = aMetrics.viewportRectWidth;
-        aTransform.height = aMetrics.viewportRectHeight;
-        aTransform.scale = aMetrics.zoomFactor;
+    }
 
-        aTransform.fixedLayerMarginTop = mLayerViewTranslation - mToolbarTranslation;
-        float bottomOfScreen = mTarget.getView().getHeight();
-        // We want to move a fixed item from "bottomOfCssViewport" to
-        // "bottomOfScreen". But also the bottom margin > 0 means that bottom
-        // fixed-pos items will move upwards.
-        aTransform.fixedLayerMarginBottom = bottomOfCssViewport(aMetrics) - bottomOfScreen;
-        //Log.v(LOGTAG, "ViewTransform is x=" + aTransform.x + " y=" + aTransform.y
-        //    + " z=" + aTransform.scale + " t=" + aTransform.fixedLayerMarginTop
-        //    + " b=" + aTransform.fixedLayerMarginBottom);
+    /* package-private */ void notifyCompositorCreated(LayerView.Compositor aCompositor) {
+        ThreadUtils.assertOnUiThread();
+        mCompositor = aCompositor;
+        dumpStateToCompositor();
     }
 
-    class DynamicToolbarAnimationTask extends RenderTask {
-        private final float mStartTranslation;
-        private final float mEndTranslation;
-        private final boolean mImmediate;
-        private final boolean mShiftLayerView;
-        private boolean mContinueAnimation;
-
-        public DynamicToolbarAnimationTask(float aTranslation, boolean aImmediate, boolean aShiftLayerView) {
-            super(false);
-            mContinueAnimation = true;
-            mStartTranslation = mToolbarTranslation;
-            mEndTranslation = aTranslation;
-            mImmediate = aImmediate;
-            mShiftLayerView = aShiftLayerView;
-        }
-
-        float getFinalToolbarTranslation() {
-            return mEndTranslation;
-        }
-
-        @Override
-        public boolean internalRun(long timeDelta, long currentFrameStartTime) {
-            if (!mContinueAnimation) {
-                return false;
-            }
+    /* package-private */ void notifyCompositorControllerOpen() {
+        mCompositorControllerOpen = true;
+        dumpStateToCompositor();
+    }
 
-            // Calculate the progress (between 0 and 1)
-            final float progress = mImmediate
-                ? 1.0f
-                : mInterpolator.getInterpolation(
-                    Math.min(1.0f, (System.nanoTime() - getStartTime())
-                                    / (float)ANIMATION_DURATION));
+    /* package-private */ void notifyCompositorDestroyed() {
+        mCompositor = null;
+    }
 
-            // This runs on the compositor thread, so we need to post the
-            // actual work to the UI thread.
-            ThreadUtils.assertNotOnUiThread();
 
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    // Move the toolbar as per the animation
-                    mToolbarTranslation = FloatUtils.interpolate(mStartTranslation, mEndTranslation, progress);
-                    fireListeners();
-
-                    if (mShiftLayerView && progress >= 1.0f) {
-                        shiftLayerView(mEndTranslation);
-                    }
-                }
-            });
-
-            mTarget.getView().requestRender();
-            if (progress >= 1.0f) {
-                mContinueAnimation = false;
-            }
-            return mContinueAnimation;
+    /* package-private */ Bitmap getBitmapOfToolbarChrome() {
+        if (mToolbarChromeProxy != null) {
+            return mToolbarChromeProxy.getBitmapOfToolbarChrome();
         }
+        return null;
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -4,57 +4,40 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.gfx.LayerView.DrawListener;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 
 import java.util.ArrayList;
-import java.util.List;
 
-class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
+class GeckoLayerClient implements LayerView.Listener
 {
     private static final String LOGTAG = "GeckoLayerClient";
-    private static int sPaintSyncId = 1;
-
-    private LayerRenderer mLayerRenderer;
-    private boolean mLayerRendererInitialized;
 
     private final Context mContext;
     private IntSize mScreenSize;
     private IntSize mWindowSize;
 
-    /*
-     * The viewport metrics being used to draw the current frame. This is only
-     * accessed by the compositor thread, and so needs no synchronisation.
-     */
-    private ImmutableViewportMetrics mFrameMetrics;
-
-    private final List<DrawListener> mDrawListeners;
-
-    /* Used as temporaries by syncViewportInfo */
-    private final ViewTransform mCurrentViewTransform;
-
     private boolean mForceRedraw;
 
     /* The current viewport metrics.
      * This is volatile so that we can read and write to it from different threads.
      * We avoid synchronization to make getting the viewport metrics from
      * the compositor as cheap as possible. The viewport is immutable so
      * we don't need to worry about anyone mutating it while we're reading from it.
      * Specifically:
@@ -78,63 +61,53 @@ class GeckoLayerClient implements LayerV
      * have the first-paint flag set, and the second paint happens concurrently with the
      * composite for the first paint, then this flag may be set to true prematurely. Fixing this
      * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
      */
     private volatile boolean mContentDocumentIsDisplayed;
 
     private SynthesizedEventState mPointerState;
 
-    @WrapForJNI(stubName = "ClearColor")
-    private volatile int mClearColor = Color.WHITE;
-
     public GeckoLayerClient(Context context, LayerView view) {
         // we can fill these in with dummy values because they are always written
         // to before being read
         mContext = context;
         mScreenSize = new IntSize(0, 0);
         mWindowSize = new IntSize(0, 0);
-        mCurrentViewTransform = new ViewTransform(0, 0, 1);
 
         mForceRedraw = true;
         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
         mViewportMetrics = new ImmutableViewportMetrics(displayMetrics)
                            .setViewportSize(view.getWidth(), view.getHeight());
 
-        mFrameMetrics = mViewportMetrics;
-
-        mDrawListeners = new ArrayList<DrawListener>();
         mToolbarAnimator = new DynamicToolbarAnimator(this);
-        mPanZoomController = PanZoomController.Factory.create(this, view);
+        mPanZoomController = PanZoomController.Factory.create(view);
         mView = view;
         mView.setListener(this);
         mContentDocumentIsDisplayed = true;
     }
 
     public void setOverscrollHandler(final Overscroll listener) {
         mPanZoomController.setOverscrollHandler(listener);
     }
 
     public void setGeckoReady(boolean ready) {
         mGeckoIsReady = ready;
     }
 
-    @Override // PanZoomTarget
     public boolean isGeckoReady() {
         return mGeckoIsReady;
     }
 
     /** Attaches to root layer so that Gecko appears. */
     @WrapForJNI(calledFrom = "gecko")
     private void onGeckoReady() {
         mGeckoIsReady = true;
 
-        mLayerRenderer = mView.getRenderer();
-
-        sendResizeEventIfNecessary(true, null);
+        sendResizeEventIfNecessary(true);
 
         // Gecko being ready is one of the two conditions (along with having an available
         // surface) that cause us to create the compositor. So here, now that we know gecko
         // is ready, call updateCompositor() to see if we can actually do the creation.
         // This needs to run on the UI thread so that the surface validity can't change on
         // us while we're in the middle of creating the compositor.
         mView.post(new Runnable() {
             @Override
@@ -142,18 +115,16 @@ class GeckoLayerClient implements LayerV
                 mPanZoomController.attach();
                 mView.updateCompositor();
             }
         });
     }
 
     public void destroy() {
         mPanZoomController.destroy();
-        mToolbarAnimator.destroy();
-        mDrawListeners.clear();
         mGeckoIsReady = false;
     }
 
     public LayerView getView() {
         return mView;
     }
 
     public FloatSize getViewportSize() {
@@ -163,32 +134,28 @@ class GeckoLayerClient implements LayerV
     /**
      * The view calls this function to indicate that the viewport changed size. It must hold the
      * monitor while calling it.
      *
      * TODO: Refactor this to use an interface. Expose that interface only to the view and not
      * to the layer client. That way, the layer client won't be tempted to call this, which might
      * result in an infinite loop.
      */
-    boolean setViewportSize(int width, int height, PointF scrollChange) {
+    boolean setViewportSize(int width, int height) {
         if (mViewportMetrics.viewportRectWidth == width &&
-            mViewportMetrics.viewportRectHeight == height &&
-            (scrollChange == null || (scrollChange.x == 0 && scrollChange.y == 0))) {
+            mViewportMetrics.viewportRectHeight == height) {
             return false;
         }
         mViewportMetrics = mViewportMetrics.setViewportSize(width, height);
-        if (scrollChange != null) {
-            mViewportMetrics = mPanZoomController.adjustScrollForSurfaceShift(mViewportMetrics, scrollChange);
-        }
 
         if (mGeckoIsReady) {
             // here we send gecko a resize message. The code in browser.js is responsible for
             // picking up on that resize event, modifying the viewport as necessary, and informing
             // us of the new viewport.
-            sendResizeEventIfNecessary(true, scrollChange);
+            sendResizeEventIfNecessary(true);
 
             // the following call also sends gecko a message, which will be processed after the resize
             // message above has updated the viewport. this message ensures that if we have just put
             // focus in a text field, we scroll the content so that the text field is in view.
             GeckoAppShell.viewSizeChanged();
         }
         return true;
     }
@@ -197,17 +164,17 @@ class GeckoLayerClient implements LayerV
         return mPanZoomController;
     }
 
     DynamicToolbarAnimator getDynamicToolbarAnimator() {
         return mToolbarAnimator;
     }
 
     /* Informs Gecko that the screen size has changed. */
-    private void sendResizeEventIfNecessary(boolean force, PointF scrollChange) {
+    private void sendResizeEventIfNecessary(boolean force) {
         DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
 
         IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
         IntSize newWindowSize = new IntSize(mViewportMetrics.viewportRectWidth,
                                             mViewportMetrics.viewportRectHeight);
 
         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
         boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
@@ -226,43 +193,16 @@ class GeckoLayerClient implements LayerV
         if (windowSizeChanged) {
             Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
         }
 
         if (mView != null) {
             mView.notifySizeChanged(mWindowSize.width, mWindowSize.height,
                                     mScreenSize.width, mScreenSize.height);
         }
-
-        final GeckoBundle data;
-        if (scrollChange != null) {
-            int id = ++sPaintSyncId;
-            if (id == 0) {
-                // never use 0 as that is the default value for "this is not
-                // a special transaction"
-                id = ++sPaintSyncId;
-            }
-            data = new GeckoBundle(3);
-            data.putDouble("x", scrollChange.x / mViewportMetrics.zoomFactor);
-            data.putDouble("y", scrollChange.y / mViewportMetrics.zoomFactor);
-            data.putInt("id", id);
-        } else {
-            data = null;
-        }
-        EventDispatcher.getInstance().dispatch("Window:Resize", data);
-    }
-
-    /**
-     * The different types of Viewport messages handled. All viewport events
-     * expect a display-port to be returned, but can handle one not being
-     * returned.
-     */
-    private enum ViewportMessageType {
-        UPDATE,       // The viewport has changed and should be entirely updated
-        PAGE_SIZE     // The viewport's page-size has changed
     }
 
     @WrapForJNI(calledFrom = "gecko")
     void contentDocumentChanged() {
         mContentDocumentIsDisplayed = false;
     }
 
     @WrapForJNI(calledFrom = "gecko")
@@ -270,93 +210,26 @@ class GeckoLayerClient implements LayerV
         return mContentDocumentIsDisplayed;
     }
 
     /** The compositor invokes this function just before compositing a frame where the document
       * is different from the document composited on the last frame. In these cases, the viewport
       * information we have in Java is no longer valid and needs to be replaced with the new
       * viewport information provided.
       */
-    @WrapForJNI
-    public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
+    @WrapForJNI(calledFrom = "ui")
+    public void updateRootFrameMetrics(float scrollX, float scrollY, float zoom,
             float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
-        synchronized (getLock()) {
-            ImmutableViewportMetrics currentMetrics = getViewportMetrics();
-
-            RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
-            RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
-
-            final ImmutableViewportMetrics newMetrics = currentMetrics
-                .setViewportOrigin(offsetX, offsetY)
-                .setZoomFactor(zoom)
-                .setPageRect(pageRect, cssPageRect);
-            // Since we have switched to displaying a different document, we need to update any
-            // viewport-related state we have lying around (i.e. mViewportMetrics).
-            // Usually this information is updated via handleViewportMessage
-            // while we remain on the same document.
-            setViewportMetrics(newMetrics, true);
-
-            // Indicate that the document is about to be composited so the
-            // LayerView background can be removed.
-            if (mView.getPaintState() == LayerView.PAINT_START) {
-                mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
-            }
-        }
-
-        mContentDocumentIsDisplayed = true;
-    }
+        RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
+        mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY)
+            .setZoomFactor(zoom)
+            .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
 
-    /** The compositor invokes this function on every frame to figure out what part of the
-      * page to display, and to inform Java of the current display port. Since it is called
-      * on every frame, it needs to be ultra-fast.
-      * It avoids taking any locks or allocating any objects. We keep around a
-      * mCurrentViewTransform so we don't need to allocate a new ViewTransform
-      * every time we're called. NOTE: we might be able to return a ImmutableViewportMetrics
-      * which would avoid the copy into mCurrentViewTransform.
-      */
-    private ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated,
-                                          int paintSyncId) {
-        // getViewportMetrics is thread safe so we don't need to synchronize.
-        // We save the viewport metrics here, so we later use it later in
-        // createFrame (which will be called by nsWindow::DrawWindowUnderlay on
-        // the native side, by the compositor). The viewport
-        // metrics can change between here and there, as it's accessed outside
-        // of the compositor thread.
-        mFrameMetrics = getViewportMetrics();
-
-        if (paintSyncId == sPaintSyncId) {
-            mToolbarAnimator.scrollChangeResizeCompleted();
-        }
-        mToolbarAnimator.populateViewTransform(mCurrentViewTransform, mFrameMetrics);
-
-        if (layersUpdated) {
-            for (DrawListener listener : mDrawListeners) {
-                listener.drawFinished();
-            }
-        }
-
-        return mCurrentViewTransform;
-    }
-
-    @WrapForJNI
-    public ViewTransform syncFrameMetrics(float scrollX, float scrollY, float zoom,
-                float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
-                int dpX, int dpY, int dpWidth, int dpHeight, float paintedResolution,
-                boolean layersUpdated, int paintSyncId)
-    {
-        // TODO: optimize this so it doesn't create so much garbage - it's a
-        // hot path
-        RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
-        synchronized (getLock()) {
-            mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY)
-                .setZoomFactor(zoom)
-                .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
-        }
-        return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution,
-                    layersUpdated, paintSyncId);
+        mToolbarAnimator.onMetricsChanged(mViewportMetrics);
+        mContentDocumentIsDisplayed = true;
     }
 
     class PointerInfo {
         // We reserve one pointer ID for the mouse, so that tests don't have
         // to worry about tracking pointer IDs if they just want to test mouse
         // event synthesization. If somebody tries to use this ID for a
         // synthesized touch event we'll throw an exception.
         public static final int RESERVED_MOUSE_POINTER_ID = 100000;
@@ -504,16 +377,19 @@ class GeckoLayerClient implements LayerV
                 }
                 break;
         }
 
         // Update the pointer with the new info
         PointerInfo info = mPointerState.pointers.get(pointerIndex);
         info.screenX = screenX;
         info.screenY = screenY;
+        if (mView != null) {
+            info.screenY += mView.getCurrentToolbarHeight();
+        }
         info.pressure = pressure;
         info.orientation = orientation;
 
         // Dispatch the event
         int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
         action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
         action |= (eventType & MotionEvent.ACTION_MASK);
         boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
@@ -562,101 +438,27 @@ class GeckoLayerClient implements LayerV
     }
 
     @WrapForJNI(calledFrom = "gecko")
     public void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) {
         synthesizeNativePointer(InputDevice.SOURCE_MOUSE, PointerInfo.RESERVED_MOUSE_POINTER_ID,
             eventType, screenX, screenY, 0, 0);
     }
 
-    @WrapForJNI
-    public LayerRenderer.Frame createFrame() {
-        // Create the shaders and textures if necessary.
-        if (!mLayerRendererInitialized) {
-            if (mLayerRenderer == null) {
-                return null;
-            }
-            mLayerRenderer.createDefaultProgram();
-            mLayerRendererInitialized = true;
-        }
-
-        try {
-            return mLayerRenderer.createFrame(mFrameMetrics);
-        } catch (Exception e) {
-            Log.w(LOGTAG, e);
-            return null;
-        }
-    }
-
-    private void geometryChanged() {
-        /* Let Gecko know if the screensize has changed */
-        sendResizeEventIfNecessary(false, null);
-    }
-
     /** Implementation of LayerView.Listener */
     @Override
-    public void surfaceChanged(int width, int height) {
+    public void surfaceChanged() {
         IntSize viewportSize = mToolbarAnimator.getViewportSize();
-        setViewportSize(viewportSize.width, viewportSize.height, null);
+        setViewportSize(viewportSize.width, viewportSize.height);
     }
 
     ImmutableViewportMetrics getViewportMetrics() {
         return mViewportMetrics;
     }
 
-    /*
-     * You must hold the monitor while calling this.
-     */
-    private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) {
-        // This class owns the viewport size and the fixed layer margins; don't let other pieces
-        // of code clobber either of them. The only place the viewport size should ever be
-        // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should
-        // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
-        // mViewportMetrics directly.
-        metrics = metrics.setViewportSize(mViewportMetrics.viewportRectWidth, mViewportMetrics.viewportRectHeight);
-        mViewportMetrics = metrics;
-
-        viewportMetricsChanged(notifyGecko);
-    }
-
-    /*
-     * You must hold the monitor while calling this.
-     */
-    private void viewportMetricsChanged(boolean notifyGecko) {
-        mToolbarAnimator.onMetricsChanged(mViewportMetrics);
-
-        mView.requestRender();
-        if (notifyGecko && mGeckoIsReady) {
-            geometryChanged();
-        }
-    }
-
-    /*
-     * Updates the viewport metrics, overriding the viewport size and margins
-     * which are normally retained when calling setViewportMetrics.
-     * You must hold the monitor while calling this.
-     */
-    void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
-        if (forceRedraw) {
-            mForceRedraw = true;
-        }
-        mViewportMetrics = metrics;
-        viewportMetricsChanged(notifyGecko);
-    }
-
-    /** Implementation of PanZoomTarget */
-    @Override
-    public void panZoomStopped() {
-        mToolbarAnimator.onPanZoomStopped();
-    }
-
-    Object getLock() {
-        return this;
-    }
-
     Matrix getMatrixForLayerRectToViewRect() {
         if (!mGeckoIsReady) {
             return null;
         }
 
         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
         PointF origin = viewportMetrics.getOrigin();
         float zoom = viewportMetrics.zoomFactor;
@@ -665,26 +467,9 @@ class GeckoLayerClient implements LayerV
         float geckoZoom = geckoViewport.zoomFactor;
 
         Matrix matrix = new Matrix();
         matrix.postTranslate(geckoOrigin.x / geckoZoom, geckoOrigin.y / geckoZoom);
         matrix.postScale(zoom, zoom);
         matrix.postTranslate(-origin.x, -origin.y);
         return matrix;
     }
-
-    @Override
-    public void setScrollingRootContent(boolean isRootContent) {
-        mToolbarAnimator.setScrollingRootContent(isRootContent);
-    }
-
-    public void addDrawListener(DrawListener listener) {
-        mDrawListeners.add(listener);
-    }
-
-    public void removeDrawListener(DrawListener listener) {
-        mDrawListeners.remove(listener);
-    }
-
-    public void setClearColor(int color) {
-        mClearColor = color;
-    }
 }
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerRenderer.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.gfx;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.opengl.GLES20;
-import android.os.SystemClock;
-import android.util.Log;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.microedition.khronos.egl.EGLConfig;
-
-/**
- * The layer renderer implements the rendering logic for a layer view.
- */
-public class LayerRenderer {
-    private static final String LOGTAG = "GeckoLayerRenderer";
-
-    /*
-     * The amount of time a frame is allowed to take to render before we declare it a dropped
-     * frame.
-     */
-    private static final int MAX_FRAME_TIME = 16;   /* 1000 ms / 60 FPS */
-    private static final long NANOS_PER_MS = 1000000;
-    private static final int MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER = 5;
-
-    private final LayerView mView;
-    private ByteBuffer mCoordByteBuffer;
-    private FloatBuffer mCoordBuffer;
-    private int mMaxTextureSize;
-
-    private long mLastFrameTime;
-    private final CopyOnWriteArrayList<RenderTask> mTasks;
-
-    // Dropped frames display
-    private final int[] mFrameTimings;
-    private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
-
-    private IntBuffer mPixelBuffer;
-    private List<LayerView.ZoomedViewListener> mZoomedViewListeners;
-    private float mLastViewLeft;
-    private float mLastViewTop;
-
-    public LayerRenderer(LayerView view) {
-        mView = view;
-
-        mTasks = new CopyOnWriteArrayList<RenderTask>();
-        mLastFrameTime = System.nanoTime();
-
-        mFrameTimings = new int[60];
-        mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
-
-        mZoomedViewListeners = new ArrayList<LayerView.ZoomedViewListener>();
-    }
-
-    public void destroy() {
-        if (mCoordByteBuffer != null) {
-            DirectBufferAllocator.free(mCoordByteBuffer);
-            mCoordByteBuffer = null;
-            mCoordBuffer = null;
-        }
-        mZoomedViewListeners.clear();
-    }
-
-    void onSurfaceCreated(EGLConfig config) {
-        createDefaultProgram();
-    }
-
-    public void createDefaultProgram() {
-        int maxTextureSizeResult[] = new int[1];
-        GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
-        mMaxTextureSize = maxTextureSizeResult[0];
-    }
-
-    public int getMaxTextureSize() {
-        return mMaxTextureSize;
-    }
-
-    public void postRenderTask(RenderTask aTask) {
-        mTasks.add(aTask);
-        mView.requestRender();
-    }
-
-    public void removeRenderTask(RenderTask aTask) {
-        mTasks.remove(aTask);
-    }
-
-    private void runRenderTasks(CopyOnWriteArrayList<RenderTask> tasks, boolean after, long frameStartTime) {
-        for (RenderTask task : tasks) {
-            if (task.runAfter != after) {
-                continue;
-            }
-
-            boolean stillRunning = task.run(frameStartTime - mLastFrameTime, frameStartTime);
-
-            // Remove the task from the list if its finished
-            if (!stillRunning) {
-                tasks.remove(task);
-            }
-        }
-    }
-
-    /** Used by robocop for testing purposes. Not for production use! */
-    IntBuffer getPixels() {
-        IntBuffer pixelBuffer = IntBuffer.allocate(mView.getWidth() * mView.getHeight());
-        synchronized (pixelBuffer) {
-            mPixelBuffer = pixelBuffer;
-            mView.requestRender();
-            try {
-                pixelBuffer.wait();
-            } catch (InterruptedException ie) {
-            }
-            mPixelBuffer = null;
-        }
-        return pixelBuffer;
-    }
-
-    private void updateDroppedFrames(long frameStartTime) {
-        int frameElapsedTime = (int)((System.nanoTime() - frameStartTime) / NANOS_PER_MS);
-
-        /* Update the running statistics. */
-        mFrameTimingsSum -= mFrameTimings[mCurrentFrame];
-        mFrameTimingsSum += frameElapsedTime;
-        mDroppedFrames -= (mFrameTimings[mCurrentFrame] + 1) / MAX_FRAME_TIME;
-        mDroppedFrames += (frameElapsedTime + 1) / MAX_FRAME_TIME;
-
-        mFrameTimings[mCurrentFrame] = frameElapsedTime;
-        mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
-
-        int averageTime = mFrameTimingsSum / mFrameTimings.length;
-    }
-
-    public Frame createFrame(ImmutableViewportMetrics metrics) {
-        return new Frame(metrics);
-    }
-
-    public class Frame {
-        // The timestamp recording the start of this frame.
-        private long mFrameStartTime;
-        // A fixed snapshot of the viewport metrics that this frame is using to render content.
-        private final ImmutableViewportMetrics mFrameMetrics;
-
-        public Frame(ImmutableViewportMetrics metrics) {
-            mFrameMetrics = metrics;
-        }
-
-        /** This function is invoked via JNI; be careful when modifying signature. */
-        @WrapForJNI
-        public void beginDrawing() {
-            mFrameStartTime = System.nanoTime();
-
-            // Run through pre-render tasks
-            runRenderTasks(mTasks, false, mFrameStartTime);
-        }
-
-
-        private void maybeRequestZoomedViewRender() {
-            // Concurrently update of mZoomedViewListeners should not be an issue here
-            // because the following line is just a short-circuit
-            if (mZoomedViewListeners.size() == 0) {
-                return;
-            }
-
-            // When scrolling fast, do not request zoomed view render to avoid to slow down
-            // the scroll in the main view.
-            // Speed is estimated using the offset changes between 2 display frame calls
-            final float viewLeft = Math.round(mFrameMetrics.getViewport().left);
-            final float viewTop = Math.round(mFrameMetrics.getViewport().top);
-            boolean shouldWaitToRender = false;
-
-            if (Math.abs(mLastViewLeft - viewLeft) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER ||
-                Math.abs(mLastViewTop - viewTop) > MAX_SCROLL_SPEED_TO_REQUEST_ZOOM_RENDER) {
-                shouldWaitToRender = true;
-            }
-
-            mLastViewLeft = viewLeft;
-            mLastViewTop = viewTop;
-
-            if (shouldWaitToRender) {
-                return;
-            }
-
-            ThreadUtils.postToUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) {
-                        listener.requestZoomedViewRender();
-                    }
-                }
-            });
-        }
-
-
-        /** This function is invoked via JNI; be careful when modifying signature. */
-        @WrapForJNI
-        public void endDrawing() {
-            PanningPerfAPI.recordFrameTime();
-
-            runRenderTasks(mTasks, true, mFrameStartTime);
-            maybeRequestZoomedViewRender();
-
-            /* Used by robocop for testing purposes */
-            IntBuffer pixelBuffer = mPixelBuffer;
-            if (pixelBuffer != null) {
-                synchronized (pixelBuffer) {
-                    pixelBuffer.position(0);
-                    GLES20.glReadPixels(0, 0, Math.round(mFrameMetrics.getWidth()),
-                                        Math.round(mFrameMetrics.getHeight()), GLES20.GL_RGBA,
-                                        GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
-                    pixelBuffer.notify();
-                }
-            }
-
-            // Remove background color once we've painted. GeckoLayerClient is
-            // responsible for setting this flag before current document is
-            // composited.
-            if (mView.getPaintState() == LayerView.PAINT_BEFORE_FIRST) {
-                mView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mView.setSurfaceBackgroundColor(Color.TRANSPARENT);
-                    }
-                });
-                mView.setPaintState(LayerView.PAINT_AFTER_FIRST);
-            }
-            mLastFrameTime = mFrameStartTime;
-        }
-    }
-
-    public void updateZoomedView(final ByteBuffer data) {
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (LayerView.ZoomedViewListener listener : mZoomedViewListeners) {
-                    data.position(0);
-                    listener.updateView(data);
-                }
-            }
-        });
-    }
-
-    public void addZoomedViewListener(LayerView.ZoomedViewListener listener) {
-        ThreadUtils.assertOnUiThread();
-        mZoomedViewListeners.add(listener);
-    }
-
-    public void removeZoomedViewListener(LayerView.ZoomedViewListener listener) {
-        ThreadUtils.assertOnUiThread();
-        mZoomedViewListeners.remove(listener);
-    }
-}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -13,67 +13,99 @@ import org.mozilla.gecko.annotation.Robo
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoAccessibility;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.os.Parcelable;
+import android.support.v4.util.SimpleArrayMap;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.InputDevice;
 import android.widget.FrameLayout;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A view rendered by the layer compositor.
  */
 public class LayerView extends FrameLayout {
     private static final String LOGTAG = "GeckoLayerView";
 
     private GeckoLayerClient mLayerClient;
     private PanZoomController mPanZoomController;
     private DynamicToolbarAnimator mToolbarAnimator;
-    private LayerRenderer mRenderer;
-    /* Must be a PAINT_xxx constant */
-    private int mPaintState;
     private FullScreenState mFullScreenState;
 
     private SurfaceView mSurfaceView;
     private TextureView mTextureView;
 
     private Listener mListener;
 
     /* This should only be modified on the Java UI thread. */
     private final Overscroll mOverscroll;
 
     private boolean mServerSurfaceValid;
     private int mWidth, mHeight;
 
     private boolean onAttachedToWindowCalled;
+    private int mDefaultClearColor = Color.WHITE;
+    /* package */ GetPixelsResult mGetPixelsResult;
+    private final List<DrawListener> mDrawListeners;
 
     /* This is written by the Gecko thread and the UI thread, and read by the UI thread. */
     @WrapForJNI(stubName = "CompositorCreated", calledFrom = "ui")
     /* package */ volatile boolean mCompositorCreated;
 
-    private class Compositor extends JNIObject {
+    //
+    // NOTE: These values are also defined in gfx/layers/ipc/UiCompositorControllerMessageTypes.h
+    //       and must be kept in sync. Any new AnimatorMessageType added here must also be added there.
+    //
+    /* package */ final static int STATIC_TOOLBAR_NEEDS_UPDATE      = 0;  // Sent from compositor when the static toolbar wants to hide.
+    /* package */ final static int STATIC_TOOLBAR_READY             = 1;  // Sent from compositor when the static toolbar image has been updated and is ready to animate.
+    /* package */ final static int TOOLBAR_HIDDEN                   = 2;  // Sent to compositor when the real toolbar has been hidden.
+    /* package */ final static int TOOLBAR_VISIBLE                  = 3;  // Sent to compositor when the real toolbar is visible.
+    /* package */ final static int TOOLBAR_SHOW                     = 4;  // Sent from compositor when the static toolbar has been made visible so the real toolbar should be shown.
+    /* package */ final static int FIRST_PAINT                      = 5;  // Sent from compositor after first paint
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_IMMEDIATELY = 6;  // Sent to compositor requesting toolbar be shown immediately
+    /* package */ final static int REQUEST_SHOW_TOOLBAR_ANIMATED    = 7;  // Sent to compositor requesting toolbar be shown animated
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_IMMEDIATELY = 8;  // Sent to compositor requesting toolbar be hidden immediately
+    /* package */ final static int REQUEST_HIDE_TOOLBAR_ANIMATED    = 9;  // Sent to compositor requesting toolbar be hidden animated
+    /* package */ final static int LAYERS_UPDATED                    = 10; // Sent from compositor when a layer has been updated
+    /* package */ final static int COMPOSITOR_CONTROLLER_OPEN       = 20; // Special message sent from UiCompositorControllerChild once it is open
+
+    private void postCompositorMessage(final int message) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mCompositor.sendToolbarAnimatorMessage(message);
+            }
+        });
+    }
+
+    /* package */ class Compositor extends JNIObject {
         public Compositor() {
         }
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         @Override protected native void disposeNative();
 
         // Gecko thread sets its Java instances; does not block UI thread.
         @WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
@@ -94,44 +126,133 @@ public class LayerView extends FrameLayo
 
         // UI thread resumes compositor and notifies Gecko thread; does not block UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         /* package */ native void syncResumeResizeCompositor(int width, int height, Object surface);
 
         @WrapForJNI(calledFrom = "any", dispatchTo = "current")
         /* package */ native void syncInvalidateAndScheduleComposite();
 
+        @WrapForJNI(calledFrom = "any", dispatchTo = "current")
+        /* package */ native void setMaxToolbarHeight(int height);
+
+        @WrapForJNI(calledFrom = "any", dispatchTo = "current")
+        /* package */ native void setPinned(boolean pinned, int reason);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void sendToolbarAnimatorMessage(int message);
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvToolbarAnimatorMessage(int message) {
+            handleToolbarAnimatorMessage(message);
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void setDefaultClearColor(int color);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void requestScreenPixels();
+
+        @WrapForJNI(calledFrom = "ui")
+        /* package */ void recvScreenPixels(int width, int height, int[] pixels) {
+            if (mGetPixelsResult != null) {
+                mGetPixelsResult.onPixelsResult(width, height, IntBuffer.wrap(pixels));
+                mGetPixelsResult = null;
+            }
+        }
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void enableLayerUpdateNotifications(boolean enable);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        /* package */ native void sendToolbarPixelsToCompositor(final int width, final int height, final int[] pixels);
+
         @WrapForJNI(calledFrom = "gecko")
         private void reattach() {
             mCompositorCreated = true;
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    // Make sure it is still valid
+                    if (mCompositorCreated) {
+                        mCompositor.setDefaultClearColor(mDefaultClearColor);
+                        mCompositor.enableLayerUpdateNotifications(!mDrawListeners.isEmpty());
+                        mToolbarAnimator.notifyCompositorCreated(mCompositor);
+                    }
+                }
+            });
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private void destroy() {
             // The nsWindow has been closed. First mark our compositor as destroyed.
             LayerView.this.mCompositorCreated = false;
 
             LayerView.this.mLayerClient.setGeckoReady(false);
 
             // Then clear out any pending calls on the UI thread by disposing on the UI thread.
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
+                    LayerView.this.mToolbarAnimator.notifyCompositorDestroyed();
+                    LayerView.this.mDrawListeners.clear();
                     disposeNative();
                 }
             });
         }
     }
 
-    private final Compositor mCompositor = new Compositor();
+    /* package */ void handleToolbarAnimatorMessage(int message) {
+        switch(message) {
+            case STATIC_TOOLBAR_NEEDS_UPDATE:
+                // Send updated toolbar image to compositor.
+                Bitmap bm = mToolbarAnimator.getBitmapOfToolbarChrome();
+                if (bm == null) {
+                    break;
+                }
+                final int width = bm.getWidth();
+                final int height = bm.getHeight();
+                int[] pixels = new int[bm.getByteCount() / 4];
+                try {
+                    bm.getPixels(pixels, /* offset */ 0, /* stride */ width, /* x */ 0, /* y */ 0, width, height);
+                    mCompositor.sendToolbarPixelsToCompositor(width, height, pixels);
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Caught exception while getting toolbar pixels from Bitmap: " + e.toString());
+                }
+                break;
+            case STATIC_TOOLBAR_READY:
+                // Hide toolbar and send TOOLBAR_HIDDEN message to compositor
+                mToolbarAnimator.onToggleChrome(false);
+                mListener.surfaceChanged();
+                postCompositorMessage(TOOLBAR_HIDDEN);
+                break;
+            case TOOLBAR_SHOW:
+                // Show toolbar.
+                mToolbarAnimator.onToggleChrome(true);
+                mListener.surfaceChanged();
+                postCompositorMessage(TOOLBAR_VISIBLE);
+                break;
+            case FIRST_PAINT:
+                setSurfaceBackgroundColor(Color.TRANSPARENT);
+                break;
+            case LAYERS_UPDATED:
+                for (DrawListener listener : mDrawListeners) {
+                    listener.drawFinished();
+                }
+                break;
+            case COMPOSITOR_CONTROLLER_OPEN:
+                mToolbarAnimator.notifyCompositorControllerOpen();
+                break;
+            default:
+                Log.e(LOGTAG,"Unhandled Toolbar Animator Message: " + message);
+                break;
+        }
+    }
 
-    /* Flags used to determine when to show the painted surface. */
-    public static final int PAINT_START = 0;
-    public static final int PAINT_BEFORE_FIRST = 1;
-    public static final int PAINT_AFTER_FIRST = 2;
+    private final Compositor mCompositor = new Compositor();
 
     public boolean shouldUseTextureView() {
         // Disable TextureView support for now as it causes panning/zooming
         // performance regressions (see bug 792259). Uncomment the code below
         // once this bug is fixed.
         return false;
 
         /*
@@ -149,49 +270,54 @@ public class LayerView extends FrameLayo
             Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString());
             return false;
         } */
     }
 
     public LayerView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mPaintState = PAINT_START;
         mFullScreenState = FullScreenState.NONE;
 
         mOverscroll = new OverscrollEdgeEffect(this);
+        mDrawListeners = new ArrayList<DrawListener>();
     }
 
     public LayerView(Context context) {
         this(context, null);
     }
 
     public void initializeView() {
         mLayerClient = new GeckoLayerClient(getContext(), this);
         if (mOverscroll != null) {
             mLayerClient.setOverscrollHandler(mOverscroll);
         }
 
         mPanZoomController = mLayerClient.getPanZoomController();
         mToolbarAnimator = mLayerClient.getDynamicToolbarAnimator();
 
-        mRenderer = new LayerRenderer(this);
-
         setFocusable(true);
         setFocusableInTouchMode(true);
 
         GeckoAccessibility.setDelegate(this);
     }
 
     /**
      * MotionEventHelper dragAsync() robocop tests can instruct
      * PanZoomController not to generate longpress events.
+     * This call comes in from a thread other than the UI thread.
+     * So dispatch to UI thread first to prevent assert in nsWindow.
      */
-    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
-        mPanZoomController.setIsLongpressEnabled(isLongpressEnabled);
+    public void setIsLongpressEnabled(final boolean isLongpressEnabled) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mPanZoomController.setIsLongpressEnabled(isLongpressEnabled);
+            }
+        });
     }
 
     private static Point getEventRadius(MotionEvent event) {
         return new Point((int)event.getToolMajor() / 2,
                          (int)event.getToolMinor() / 2);
     }
 
     public void showSurface() {
@@ -203,19 +329,16 @@ public class LayerView extends FrameLayo
         // Fix this if TextureView support is turned back on above
         mSurfaceView.setVisibility(View.INVISIBLE);
     }
 
     public void destroy() {
         if (mLayerClient != null) {
             mLayerClient.destroy();
         }
-        if (mRenderer != null) {
-            mRenderer.destroy();
-        }
     }
 
     @Override
     public void dispatchDraw(final Canvas canvas) {
         super.dispatchDraw(canvas);
 
         // We must have a layer client to get valid viewport metrics
         if (mLayerClient != null && mOverscroll != null) {
@@ -224,22 +347,16 @@ public class LayerView extends FrameLayo
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             requestFocus();
         }
 
-        if (mToolbarAnimator != null && mToolbarAnimator.onInterceptTouchEvent(event)) {
-            if (mPanZoomController != null) {
-                mPanZoomController.onMotionEventVelocity(event.getEventTime(), mToolbarAnimator.getVelocity());
-            }
-            return true;
-        }
         if (!mLayerClient.isGeckoReady()) {
             // If gecko isn't loaded yet, don't try sending events to the
             // native code because it's just going to crash
             return true;
         }
         if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) {
             return true;
         }
@@ -335,16 +452,20 @@ public class LayerView extends FrameLayo
         super.onDetachedFromWindow();
 
         onAttachedToWindowCalled = false;
     }
 
     // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
     GeckoLayerClient getLayerClient() { return mLayerClient; }
 
+    /* package */ boolean isGeckoReady() {
+        return mLayerClient.isGeckoReady();
+    }
+
     public PanZoomController getPanZoomController() { return mPanZoomController; }
     public DynamicToolbarAnimator getDynamicToolbarAnimator() { return mToolbarAnimator; }
 
     public ImmutableViewportMetrics getViewportMetrics() {
         return mLayerClient.getViewportMetrics();
     }
 
     public Matrix getMatrixForLayerRectToViewRect() {
@@ -358,45 +479,38 @@ public class LayerView extends FrameLayo
     }
 
     public void requestRender() {
         if (mCompositorCreated) {
             mCompositor.syncInvalidateAndScheduleComposite();
         }
     }
 
-    public void postRenderTask(RenderTask task) {
-        mRenderer.postRenderTask(task);
-    }
-
-    public void removeRenderTask(RenderTask task) {
-        mRenderer.removeRenderTask(task);
-    }
-
-    public int getMaxTextureSize() {
-        return mRenderer.getMaxTextureSize();
+    public interface GetPixelsResult {
+        public void onPixelsResult(int width, int height, IntBuffer pixels);
     }
 
-    /** Used by robocop for testing purposes. Not for production use! */
     @RobocopTarget
-    public IntBuffer getPixels() {
-        return mRenderer.getPixels();
-    }
+    public void getPixels(final GetPixelsResult getPixelsResult) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    getPixels(getPixelsResult);
+                }
+            });
+            return;
+        }
 
-    /* paintState must be a PAINT_xxx constant. */
-    public void setPaintState(int paintState) {
-        mPaintState = paintState;
-    }
-
-    public int getPaintState() {
-        return mPaintState;
-    }
-
-    public LayerRenderer getRenderer() {
-        return mRenderer;
+        if (mCompositorCreated) {
+            mGetPixelsResult = getPixelsResult;
+            mCompositor.requestScreenPixels();
+        } else {
+            getPixelsResult.onPixelsResult(0, 0, null);
+        }
     }
 
     public void setListener(Listener listener) {
         mListener = listener;
     }
 
     Listener getListener() {
         return mListener;
@@ -451,16 +565,19 @@ public class LayerView extends FrameLayo
         }
 
         // Only try to create the compositor if we have a valid surface and gecko is up. When these
         // two conditions are satisfied, we can be relatively sure that the compositor creation will
         // happen without needing to block anywhere.
         if (mServerSurfaceValid && getLayerClient().isGeckoReady()) {
             mCompositorCreated = true;
             mCompositor.createCompositor(mWidth, mHeight, getSurface());
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
+            mCompositor.enableLayerUpdateNotifications(!mDrawListeners.isEmpty());
+            mToolbarAnimator.notifyCompositorCreated(mCompositor);
         }
     }
 
     /* When using a SurfaceView (mSurfaceView != null), resizing happens in two
      * phases. First, the LayerView changes size, then, often some frames later,
      * the SurfaceView changes size. Because of this, we need to split the
      * resize into two phases to avoid jittering.
      *
@@ -491,17 +608,17 @@ public class LayerView extends FrameLayo
             mOverscroll.setSize(width, height);
         }
     }
 
     private void surfaceChanged(int width, int height) {
         serverSurfaceChanged(width, height);
 
         if (mListener != null) {
-            mListener.surfaceChanged(width, height);
+            mListener.surfaceChanged();
         }
 
         if (mOverscroll != null) {
             mOverscroll.setSize(width, height);
         }
     }
 
     void notifySizeChanged(int windowWidth, int windowHeight, int screenWidth, int screenHeight) {
@@ -539,30 +656,18 @@ public class LayerView extends FrameLayo
 
     public Object getSurface() {
       if (mSurfaceView != null) {
         return mSurfaceView.getHolder().getSurface();
       }
       return null;
     }
 
-    // This method is called on the Gecko main thread.
-    @WrapForJNI(calledFrom = "gecko")
-    private static void updateZoomedView(ByteBuffer data) {
-        LayerView layerView = GeckoAppShell.getLayerView();
-        if (layerView != null) {
-            LayerRenderer layerRenderer = layerView.getRenderer();
-            if (layerRenderer != null) {
-                layerRenderer.updateZoomedView(data);
-            }
-        }
-    }
-
     public interface Listener {
-        void surfaceChanged(int width, int height);
+        void surfaceChanged();
     }
 
     private class SurfaceListener implements SurfaceHolder.Callback {
         @Override
         public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                                 int height) {
             onSizeChanged(width, height);
         }
@@ -578,19 +683,19 @@ public class LayerView extends FrameLayo
     }
 
     /* A subclass of SurfaceView to listen to layout changes, as
      * View.OnLayoutChangeListener requires API level 11.
      */
     private class LayerSurfaceView extends SurfaceView {
         private LayerView mParent;
 
-        public LayerSurfaceView(Context aContext, LayerView aParent) {
-            super(aContext);
-            mParent = aParent;
+        public LayerSurfaceView(Context context, LayerView parent) {
+            super(context);
+            mParent = parent;
         }
 
         @Override
         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
             super.onLayout(changed, left, top, right, bottom);
             if (changed && mParent.mServerSurfaceValid) {
                 mParent.surfaceChanged(right - left, bottom - top);
             }
@@ -618,23 +723,51 @@ public class LayerView extends FrameLayo
 
         @Override
         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
         }
     }
 
     @RobocopTarget
-    public void addDrawListener(DrawListener listener) {
-        mLayerClient.addDrawListener(listener);
+    public void addDrawListener(final DrawListener listener) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    addDrawListener(listener);
+                }
+            });
+            return;
+        }
+
+        boolean wasEmpty = mDrawListeners.isEmpty();
+        mDrawListeners.add(listener);
+        if (mCompositorCreated && wasEmpty) {
+            mCompositor.enableLayerUpdateNotifications(true);
+        }
     }
 
     @RobocopTarget
-    public void removeDrawListener(DrawListener listener) {
-        mLayerClient.removeDrawListener(listener);
+    public void removeDrawListener(final DrawListener listener) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    removeDrawListener(listener);
+                }
+            });
+            return;
+        }
+
+        boolean notEmpty = mDrawListeners.isEmpty();
+        mDrawListeners.remove(listener);
+        if (mCompositorCreated && notEmpty && mDrawListeners.isEmpty()) {
+            mCompositor.enableLayerUpdateNotifications(false);
+        }
     }
 
     @RobocopTarget
     public static interface DrawListener {
         public void drawFinished();
     }
 
     @Override
@@ -660,49 +793,33 @@ public class LayerView extends FrameLayo
     public void setFullScreenState(FullScreenState state) {
         mFullScreenState = state;
     }
 
     public boolean isFullScreen() {
         return mFullScreenState != FullScreenState.NONE;
     }
 
-    public void setMaxTranslation(float aMaxTranslation) {
-        mToolbarAnimator.setMaxTranslation(aMaxTranslation);
-    }
-
-    public void setSurfaceTranslation(float translation) {
-        setTranslationY(translation);
+    public void setMaxToolbarHeight(int maxHeight) {
+        mToolbarAnimator.setMaxToolbarHeight(maxHeight);
     }
 
-    public float getSurfaceTranslation() {
-        return getTranslationY();
-    }
-
-    // Public hooks for dynamic toolbar translation
-
-    public interface DynamicToolbarListener {
-        public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation);
-        public void onPanZoomStopped();
-        public void onMetricsChanged(ImmutableViewportMetrics viewport);
+    public int getCurrentToolbarHeight() {
+        return mToolbarAnimator.getCurrentToolbarHeight();
     }
 
-    // Public hooks for zoomed view
-
-    public interface ZoomedViewListener {
-        public void requestZoomedViewRender();
-        public void updateView(ByteBuffer data);
-    }
-
-    public void addZoomedViewListener(ZoomedViewListener listener) {
-        mRenderer.addZoomedViewListener(listener);
-    }
+    public void setClearColor(final int color) {
+        if (!ThreadUtils.isOnUiThread()) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    setClearColor(color);
+                }
+            });
+            return;
+        }
 
-    public void removeZoomedViewListener(ZoomedViewListener listener) {
-        mRenderer.removeZoomedViewListener(listener);
-    }
-
-    public void setClearColor(int color) {
-        if (mLayerClient != null) {
-            mLayerClient.setClearColor(color);
+        mDefaultClearColor = color;
+        if (mCompositorCreated) {
+            mCompositor.setDefaultClearColor(mDefaultClearColor);
         }
-    }
+   }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -18,17 +18,16 @@ import org.json.JSONObject;
 import android.graphics.PointF;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.InputDevice;
 
 class NativePanZoomController extends JNIObject implements PanZoomController {
-    private final PanZoomTarget mTarget;
     private final LayerView mView;
     private boolean mDestroyed;
     private Overscroll mOverscroll;
     boolean mNegateWheelScroll;
     private float mPointerScrollFactor;
     private PrefsHelper.PrefHandler mPrefsObserver;
     private long mLastDownTime;
     private static final float MAX_SCROLL = 0.075f * GeckoAppShell.getDpi();
@@ -45,19 +44,16 @@ class NativePanZoomController extends JN
             float x, float y,
             float hScroll, float vScroll);
 
     @WrapForJNI(calledFrom = "ui")
     private native boolean handleMouseEvent(
             int action, long time, int metaState,
             float x, float y, int buttons);
 
-    @WrapForJNI(calledFrom = "ui")
-    private native void handleMotionEventVelocity(long time, float ySpeed);
-
     private boolean handleMotionEvent(MotionEvent event) {
         if (mDestroyed) {
             return false;
         }
 
         final int action = event.getActionMasked();
         final int count = event.getPointerCount();
 
@@ -106,17 +102,18 @@ class NativePanZoomController extends JN
 
         if (count <= 0) {
             return false;
         }
 
         final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
         event.getPointerCoords(0, coords);
         final float x = coords.x;
-        final float y = coords.y;
+        // Scroll events are not adjusted by the AndroidDyanmicToolbarAnimator so adjust the offset here.
+        final float y = coords.y - mView.getCurrentToolbarHeight();
 
         final float flipFactor = mNegateWheelScroll ? -1.0f : 1.0f;
         final float hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL) * flipFactor * mPointerScrollFactor;
         final float vScroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * flipFactor * mPointerScrollFactor;
 
         return handleScrollEvent(event.getEventTime(), event.getMetaState(), x, y, hScroll, vScroll);
     }
 
@@ -135,18 +132,17 @@ class NativePanZoomController extends JN
         event.getPointerCoords(0, coords);
         final float x = coords.x;
         final float y = coords.y;
 
         return handleMouseEvent(event.getActionMasked(), event.getEventTime(), event.getMetaState(), x, y, event.getButtonState());
     }
 
 
-    NativePanZoomController(PanZoomTarget target, View view) {
-        mTarget = target;
+    NativePanZoomController(View view) {
         mView = (LayerView) view;
         mDestroyed = true;
 
         String[] prefs = { "ui.scrolling.negate_wheel_scroll" };
         mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
             @Override public void prefValue(String pref, boolean value) {
                 if (pref.equals("ui.scrolling.negate_wheel_scroll")) {
                     mNegateWheelScroll = value;
@@ -192,28 +188,23 @@ class NativePanZoomController extends JN
                    (action == MotionEvent.ACTION_HOVER_ENTER) ||
                    (action == MotionEvent.ACTION_HOVER_EXIT)) {
             return handleMouseEvent(event);
         } else {
             return false;
         }
     }
 
-    @Override
-    public void onMotionEventVelocity(final long aEventTime, final float aSpeedY) {
-        handleMotionEventVelocity(aEventTime, aSpeedY);
-    }
-
     @Override @WrapForJNI(calledFrom = "ui") // PanZoomController
     public void destroy() {
         if (mPrefsObserver != null) {
             PrefsHelper.removeObserver(mPrefsObserver);
             mPrefsObserver = null;
         }
-        if (mDestroyed || !mTarget.isGeckoReady()) {
+        if (mDestroyed || !mView.isGeckoReady()) {
             return;
         }
         mDestroyed = true;
         disposeNative();
     }
 
     @Override
     public void attach() {
@@ -233,25 +224,16 @@ class NativePanZoomController extends JN
 
     @Override // PanZoomController
     public void setIsLongpressEnabled(boolean isLongpressEnabled) {
         if (!mDestroyed) {
             nativeSetIsLongpressEnabled(isLongpressEnabled);
         }
     }
 
-    @WrapForJNI(calledFrom = "ui")
-    private native void adjustScrollForSurfaceShift(float aX, float aY);
-
-    @Override // PanZoomController
-    public ImmutableViewportMetrics adjustScrollForSurfaceShift(ImmutableViewportMetrics aMetrics, PointF aShift) {
-        adjustScrollForSurfaceShift(aShift.x, aShift.y);
-        return aMetrics.offsetViewportByAndClamp(aShift.x, aShift.y);
-    }
-
     @WrapForJNI
     private void updateOverscrollVelocity(final float x, final float y) {
         if (mOverscroll != null) {
             if (ThreadUtils.isOnUiThread() == true) {
                 mOverscroll.setVelocity(x * 1000.0f, Overscroll.Axis.X);
                 mOverscroll.setVelocity(y * 1000.0f, Overscroll.Axis.Y);
             } else {
                 ThreadUtils.postToUiThread(new Runnable() {
@@ -279,21 +261,16 @@ class NativePanZoomController extends JN
                         mOverscroll.setDistance(x, Overscroll.Axis.X);
                         mOverscroll.setDistance(y, Overscroll.Axis.Y);
                     }
                 });
             }
         }
     }
 
-    @WrapForJNI(calledFrom = "ui")
-    private void setScrollingRootContent(final boolean isRootContent) {
-        mTarget.setScrollingRootContent(isRootContent);
-    }
-
     /**
      * Active SelectionCaretDrag requires DynamicToolbarAnimator to be pinned
      * to avoid unwanted scroll interactions.
      */
     @WrapForJNI(calledFrom = "gecko")
     private void onSelectionDragState(boolean state) {
         mView.getDynamicToolbarAnimator().setPinned(state, PinReason.CARET_DRAG);
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/OverscrollEdgeEffect.java
@@ -119,21 +119,22 @@ public class OverscrollEdgeEffect implem
 
     @Override
     public void draw(final Canvas canvas, final ImmutableViewportMetrics metrics) {
         if (metrics == null) {
             return;
         }
 
         PointF visibleEnd = mView.getDynamicToolbarAnimator().getVisibleEndOfLayerView();
+        float toolbarEnd = (float)mView.getDynamicToolbarAnimator().getCurrentToolbarHeight();
 
         // If we're pulling an edge, or fading it out, draw!
         boolean invalidate = false;
         if (!mEdges[TOP].isFinished()) {
-            invalidate |= draw(mEdges[TOP], canvas, 0, 0, 0);
+            invalidate |= draw(mEdges[TOP], canvas, 0, toolbarEnd, 0);
         }
 
         if (!mEdges[BOTTOM].isFinished()) {
             invalidate |= draw(mEdges[BOTTOM], canvas, visibleEnd.x, visibleEnd.y, 180);
         }
 
         if (!mEdges[LEFT].isFinished()) {
             invalidate |= draw(mEdges[LEFT], canvas, 0, visibleEnd.y, 270);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomController.java
@@ -12,26 +12,23 @@ import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 
 public interface PanZoomController {
     // Threshold for sending touch move events to content
     public static final float CLICK_THRESHOLD = 1 / 50f * GeckoAppShell.getDpi();
 
     static class Factory {
-        static PanZoomController create(PanZoomTarget target, View view) {
-            return new NativePanZoomController(target, view);
+        static PanZoomController create(View view) {
+            return new NativePanZoomController(view);
         }
     }
 
     public void destroy();
     public void attach();
 
     public boolean onTouchEvent(MotionEvent event);
     public boolean onMotionEvent(MotionEvent event);
-    public void onMotionEventVelocity(final long aEventTime, final float aSpeedY);
 
     public void setOverscrollHandler(final Overscroll controller);
 
     public void setIsLongpressEnabled(boolean isLongpressEnabled);
-
-    public ImmutableViewportMetrics adjustScrollForSurfaceShift(ImmutableViewportMetrics aMetrics, PointF aShift);
 }
deleted file mode 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanZoomTarget.java
+++ /dev/null
@@ -1,15 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.gfx;
-
-import android.graphics.Matrix;
-import android.graphics.PointF;
-
-public interface PanZoomTarget {
-    public void panZoomStopped();
-    public boolean isGeckoReady();
-    public void setScrollingRootContent(boolean isRootContent);
-}
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeDriver.java
@@ -12,30 +12,33 @@ import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.IntBuffer;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.lang.StringBuffer;
+import java.lang.Math;
 
+import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanningPerfAPI;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import android.app.Activity;
 import android.util.Log;
 import android.view.View;
 
 import com.robotium.solo.Solo;
 
-public class FennecNativeDriver implements Driver {
+public class FennecNativeDriver implements Driver, LayerView.GetPixelsResult {
     private static final int FRAME_TIME_THRESHOLD = 25;     // allow 25ms per frame (40fps)
 
     private final Activity mActivity;
     private final Solo mSolo;
     private final String mRootPath;
 
     private static String mLogFile;
     private static LogLevel mLogLevel = LogLevel.INFO;
@@ -79,16 +82,24 @@ public class FennecNativeDriver implemen
             mGeckoTop = pos[1];
             mGeckoLeft = pos[0];
             mGeckoWidth = geckoLayout.getWidth();
             mGeckoHeight = geckoLayout.getHeight();
             mGeckoInfo = true;
         } else {
             throw new RoboCopException("Unable to find view gecko_layout");
         }
+        View toolbarLayout = mActivity.findViewById(R.id.browser_chrome);
+        if (toolbarLayout != null) {
+          // Need to remove the height of the toolbar since the top part of
+          // the gecko_layout is hidden under the toolbar.
+          final int toolbarHeight = toolbarLayout.getHeight();
+          mGeckoTop += toolbarHeight;
+          mGeckoHeight -= toolbarHeight;
+        }
     }
 
     @Override
     public int getGeckoTop() {
         if (!mGeckoInfo) {
             getGeckoInfo();
         }
         return mGeckoTop;
@@ -173,29 +184,115 @@ public class FennecNativeDriver implemen
             log(LogLevel.WARN, "getSurfaceView could not find LayerView");
             for (final View v : mSolo.getViews()) {
                 log(LogLevel.WARN, "  View: " + v);
             }
         }
         return layerView;
     }
 
+    private volatile boolean mGotPixelsResult;
+    private int mPixelsWidth;
+    private int mPixelsHeight;
+    private IntBuffer mPixelsResult;
+
+    @Override
+    public synchronized void onPixelsResult(int aWidth, int aHeight, IntBuffer aPixels) {
+        mPixelsWidth = aWidth;
+        mPixelsHeight = aHeight;
+        mPixelsResult = aPixels;
+        mGotPixelsResult = true;
+        notifyAll();
+    }
+
+    private static final int COLOR_DEVIATION = 3;
+
+    // Due to anti-aliasing, border pixels can be blended. This should filter them out.
+    private static boolean differentColor(final int c1, final int c2) {
+        int r1 = c1 & 0xFF;
+        int b1 = (c1 & 0xFF00) >> 8;
+        int g1 = (c1 & 0xFF0000) >> 16;
+        int r2 = c2 & 0xFF;
+        int b2 = (c2 & 0xFF00) >> 8;
+        int g2 = (c2 & 0xFF0000) >> 16;
+        return (Math.abs(r1 - r2) > COLOR_DEVIATION) ||
+               (Math.abs(g1 - g2) > COLOR_DEVIATION) ||
+               (Math.abs(b1 - b2) > COLOR_DEVIATION);
+    }
+
+    private void logPixels(final IntBuffer pixelBuffer, final int w, final int h) {
+        pixelBuffer.position(0);
+
+        int prevFirstRowColor = 0xFF000000; // Color not found in test page.
+        int prevColor = 0xFF000000;
+        StringBuffer sb = null;
+        for (int y = 0; y < h; y++) {
+             for (int x = 0; x < w; x++) {
+                 int agbr = pixelBuffer.get();
+                 // Starting a new row of squares
+                 if ((x == 0) && (prevFirstRowColor != agbr)) {
+                     sb = new StringBuffer();
+                     prevFirstRowColor = agbr;
+                 }
+
+                 // the  current pixel in this row is a different color than the previous one so log it.
+                 if ((sb != null) && differentColor(prevColor, agbr)) {
+                     sb.append(String.format("(%3d,%3d,%3d) ", (agbr & 0xFF), (agbr & 0xFF00) >> 8, (agbr & 0xFF0000) >> 16));
+                 }
+                 prevColor = agbr;
+             }
+             if (sb != null) {
+                 // Add what row is being logged.
+                 sb.append("h:").append(h - y);
+                 log(LogLevel.INFO,sb.toString());
+                 sb = null;
+             }
+        }
+    }
+
     @Override
     public PaintedSurface getPaintedSurface() {
         final LayerView view = getSurfaceView();
         if (view == null) {
             return null;
         }
 
-        final IntBuffer pixelBuffer = view.getPixels();
+        view.getPixels(this);
+
+        synchronized (this) {
+            while (!mGotPixelsResult) {
+                try {
+                    wait();
+                } catch (InterruptedException ie) {
+                }
+            }
+        }
+
+        final IntBuffer pixelBuffer = mPixelsResult;
+        int w = mPixelsWidth;
+        int h = mPixelsHeight;
+
+        mGotPixelsResult = false;
+        mPixelsWidth = 0;
+        mPixelsHeight = 0;
+        mPixelsResult = null;
+
+
+        if ((pixelBuffer == null) || (w == 0) || (h == 0)) {
+            return null;
+        }
+
+        // The page used in robocop tests is a grid of different colored squares.
+        // The function will log the color of each square found in the screen capture.
+        // This allows the screen capture to be examined in the log output in a human
+        // readable format.
+        // logPixels(pixelBuffer, w, h);
 
         // now we need to (1) flip the image, because GL likes to do things up-side-down,
         // and (2) rearrange the bits from AGBR-8888 to ARGB-8888.
-        int w = view.getWidth();
-        int h = view.getHeight();
         pixelBuffer.position(0);
         String mapFile = mRootPath + "/pixels.map";
 
         FileOutputStream fos = null;
         BufferedOutputStream bos = null;
         DataOutputStream dos = null;
         try {
             fos = new FileOutputStream(mapFile);
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -176,16 +176,27 @@ MultiTouchInput::MultiTouchInput(const W
   mTouches.AppendElement(SingleTouchData(0,
                                          ViewAs<ScreenPixel>(aMouseEvent.mRefPoint,
                                                              PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent),
                                          ScreenSize(1, 1),
                                          180.0f,
                                          1.0f));
 }
 
+void
+MultiTouchInput::Translate(const ScreenPoint& aTranslation)
+{
+  const int32_t xTranslation = (int32_t)(aTranslation.x + 0.5f);
+  const int32_t yTranslation = (int32_t)(aTranslation.y + 0.5f);
+
+  for (auto iter = mTouches.begin(); iter != mTouches.end(); iter++) {
+    iter->mScreenPoint.MoveBy(xTranslation, yTranslation);
+  }
+}
+
 WidgetTouchEvent
 MultiTouchInput::ToWidgetTouchEvent(nsIWidget* aWidget) const
 {
   MOZ_ASSERT(NS_IsMainThread(),
              "Can only convert To WidgetTouchEvent on main thread");
 
   EventMessage touchEventMessage = eVoidEvent;
   switch (mType) {
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -203,16 +203,17 @@ public:
   explicit MultiTouchInput(const WidgetTouchEvent& aTouchEvent);
   // This conversion from WidgetMouseEvent to MultiTouchInput is needed because
   // on the B2G emulator we can only receive mouse events, but we need to be
   // able to pan correctly. To do this, we convert the events into a format that
   // the panning code can handle. This code is very limited and only supports
   // SingleTouchData. It also sends garbage for the identifier, radius, force
   // and rotation angle.
   explicit MultiTouchInput(const WidgetMouseEvent& aMouseEvent);
+  void Translate(const ScreenPoint& aTranslation);
 
   WidgetTouchEvent ToWidgetTouchEvent(nsIWidget* aWidget) const;
   WidgetMouseEvent ToWidgetMouseEvent(nsIWidget* aWidget) const;
 
   // Return the index into mTouches of the SingleTouchData with the given
   // identifier, or -1 if there is no such SingleTouchData.
   int32_t IndexOfTouch(int32_t aTouchIdentifier);
 
--- a/widget/android/AndroidCompositorWidget.cpp
+++ b/widget/android/AndroidCompositorWidget.cpp
@@ -5,65 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AndroidCompositorWidget.h"
 #include "nsWindow.h"
 
 namespace mozilla {
 namespace widget {
 
-void
-AndroidCompositorWidget::SetFirstPaintViewport(const LayerIntPoint& aOffset,
-                                               const CSSToLayerScale& aZoom,
-                                               const CSSRect& aCssPageRect)
-{
-    auto layerClient = static_cast<nsWindow*>(RealWidget())->GetLayerClient();
-    if (!layerClient) {
-        return;
-    }
-
-    layerClient->SetFirstPaintViewport(
-            float(aOffset.x), float(aOffset.y), aZoom.scale, aCssPageRect.x,
-            aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost());
-}
-
-void
-AndroidCompositorWidget::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
-                                          const CSSToParentLayerScale& aZoom,
-                                          const CSSRect& aCssPageRect,
-                                          const CSSRect& aDisplayPort,
-                                          const CSSToLayerScale& aPaintedResolution,
-                                          bool aLayersUpdated,
-                                          int32_t aPaintSyncId,
-                                          ScreenMargin& aFixedLayerMargins)
-{
-    auto layerClient = static_cast<nsWindow*>(RealWidget())->GetLayerClient();
-    if (!layerClient) {
-        return;
-    }
-
-    // convert the displayport rect from document-relative CSS pixels to
-    // document-relative device pixels
-    LayerIntRect dp = gfx::RoundedToInt(aDisplayPort * aPaintedResolution);
-
-    java::ViewTransform::LocalRef viewTransform = layerClient->SyncFrameMetrics(
-            aScrollOffset.x, aScrollOffset.y, aZoom.scale,
-            aCssPageRect.x, aCssPageRect.y,
-            aCssPageRect.XMost(), aCssPageRect.YMost(),
-            dp.x, dp.y, dp.width, dp.height,
-            aPaintedResolution.scale, aLayersUpdated, aPaintSyncId);
-
-    MOZ_ASSERT(viewTransform, "No view transform object!");
-
-    aFixedLayerMargins.top = viewTransform->FixedLayerMarginTop();
-    aFixedLayerMargins.right = viewTransform->FixedLayerMarginRight();
-    aFixedLayerMargins.bottom = viewTransform->FixedLayerMarginBottom();
-    aFixedLayerMargins.left = viewTransform->FixedLayerMarginLeft();
-}
-
 EGLNativeWindowType
 AndroidCompositorWidget::GetEGLNativeWindow()
 {
   return (EGLNativeWindowType)mWidget->GetNativeData(NS_JAVA_SURFACE);
 }
 
 EGLNativeWindowType
 AndroidCompositorWidget::GetPresentationEGLSurface()
--- a/widget/android/AndroidCompositorWidget.h
+++ b/widget/android/AndroidCompositorWidget.h
@@ -24,29 +24,16 @@ namespace widget {
  */
 class AndroidCompositorWidget final : public InProcessCompositorWidget
 {
 public:
     using InProcessCompositorWidget::InProcessCompositorWidget;
 
     AndroidCompositorWidget* AsAndroid() override { return this; }
 
-    void SetFirstPaintViewport(const LayerIntPoint& aOffset,
-                               const CSSToLayerScale& aZoom,
-                               const CSSRect& aCssPageRect);
-
-    void SyncFrameMetrics(const ParentLayerPoint& aScrollOffset,
-                          const CSSToParentLayerScale& aZoom,
-                          const CSSRect& aCssPageRect,
-                          const CSSRect& aDisplayPort,
-                          const CSSToLayerScale& aPaintedResolution,
-                          bool aLayersUpdated,
-                          int32_t aPaintSyncId,
-                          ScreenMargin& aFixedLayerMargins);
-
     EGLNativeWindowType GetEGLNativeWindow();
 
     EGLSurface GetPresentationEGLSurface();
     void SetPresentationEGLSurface(EGLSurface aVal);
 
     ANativeWindow* GetPresentationANativeWindow();
 };
 
--- a/widget/android/AndroidContentController.cpp
+++ b/widget/android/AndroidContentController.cpp
@@ -21,98 +21,32 @@ namespace widget {
 void
 AndroidContentController::Destroy()
 {
     mAndroidWindow = nullptr;
     ChromeProcessController::Destroy();
 }
 
 void
-AndroidContentController::DispatchSingleTapToObservers(const LayoutDevicePoint& aPoint,
-                                                       const ScrollableLayerGuid& aGuid) const
-{
-    nsIContent* content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
-    nsPresContext* context = content
-        ? mozilla::layers::APZCCallbackHelper::GetPresContextForContent(content)
-        : nullptr;
-
-    if (!context) {
-      return;
-    }
-
-    CSSPoint point = mozilla::layers::APZCCallbackHelper::ApplyCallbackTransform(
-        aPoint / context->CSSToDevPixelScale(), aGuid);
-
-    nsPresContext* rcdContext = context->GetToplevelContentDocumentPresContext();
-    if (rcdContext && rcdContext->PresShell()->ScaleToResolution()) {
-        // We need to convert from the root document to the root content document,
-        // by unapplying the resolution that's on the content document.
-        const float resolution = rcdContext->PresShell()->GetResolution();
-        point.x /= resolution;
-        point.y /= resolution;
-    }
-
-    CSSIntPoint rounded = RoundedToInt(point);
-    nsAppShell::PostEvent([rounded] {
-        nsCOMPtr<nsIObserverService> obsServ =
-            mozilla::services::GetObserverService();
-        if (!obsServ) {
-            return;
-        }
-
-        nsPrintfCString data("{\"x\":%d,\"y\":%d}", rounded.x, rounded.y);
-        obsServ->NotifyObservers(nullptr, "Gesture:SingleTap",
-                                 NS_ConvertASCIItoUTF16(data).get());
-    });
-}
-
-void
-AndroidContentController::HandleTap(TapType aType, const LayoutDevicePoint& aPoint,
-                                    Modifiers aModifiers,
-                                    const ScrollableLayerGuid& aGuid,
-                                    uint64_t aInputBlockId)
-{
-    // This function will get invoked first on the Java UI thread, and then
-    // again on the main thread (because of the code in ChromeProcessController::
-    // HandleTap). We want to post the SingleTap message once; it can be
-    // done from either thread but we need access to the callback transform
-    // so we do it from the main thread.
-    if (NS_IsMainThread() &&
-        (aType == TapType::eSingleTap || aType == TapType::eSecondTap)) {
-        DispatchSingleTapToObservers(aPoint, aGuid);
-    }
-
-    ChromeProcessController::HandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId);
-}
-
-void
 AndroidContentController::UpdateOverscrollVelocity(const float aX, const float aY, const bool aIsRootContent)
 {
   if (aIsRootContent && mAndroidWindow) {
     mAndroidWindow->UpdateOverscrollVelocity(aX, aY);
   }
 }
 
 void
 AndroidContentController::UpdateOverscrollOffset(const float aX, const float aY, const bool aIsRootContent)
 {
   if (aIsRootContent && mAndroidWindow) {
     mAndroidWindow->UpdateOverscrollOffset(aX, aY);
   }
 }
 
 void
-AndroidContentController::SetScrollingRootContent(const bool isRootContent)
-{
-  if (mAndroidWindow) {
-    mAndroidWindow->SetScrollingRootContent(isRootContent);
-  }
-}
-
-void
 AndroidContentController::NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                                APZStateChange aChange,
                                                int aArg)
 {
   // This function may get invoked twice, if the first invocation is not on
   // the main thread then the ChromeProcessController version of this function
   // will redispatch to the main thread. We want to make sure that our handling
   // only happens on the main thread.
--- a/widget/android/AndroidContentController.h
+++ b/widget/android/AndroidContentController.h
@@ -29,27 +29,21 @@ public:
                              mozilla::layers::APZEventState* aAPZEventState,
                              mozilla::layers::IAPZCTreeManager* aAPZCTreeManager)
       : mozilla::layers::ChromeProcessController(aWindow, aAPZEventState, aAPZCTreeManager)
       , mAndroidWindow(aWindow)
     {}
 
     // ChromeProcessController methods
     virtual void Destroy() override;
-    void HandleTap(TapType aType, const LayoutDevicePoint& aPoint, Modifiers aModifiers,
-                   const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId) override;
     void UpdateOverscrollVelocity(const float aX, const float aY, const bool aIsRootContent) override;
     void UpdateOverscrollOffset(const float aX, const float aY, const bool aIsRootContent) override;
-    void SetScrollingRootContent(const bool isRootContent) override;
     void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                               APZStateChange aChange,
                               int aArg) override;
 private:
     nsWindow* mAndroidWindow;
-
-    void DispatchSingleTapToObservers(const LayoutDevicePoint& aPoint,
-                                      const ScrollableLayerGuid& aGuid) const;
 };
 
 } // namespace widget
 } // namespace mozilla
 
 #endif
--- a/widget/android/GeneratedJNINatives.h
+++ b/widget/android/GeneratedJNINatives.h
@@ -346,17 +346,17 @@ const JNINativeMethod SurfaceTextureList
             mozilla::jni::NativeStub<SurfaceTextureListener::OnFrameAvailable_t, Impl>
             ::template Wrap<&Impl::OnFrameAvailable>)
 };
 
 template<class Impl>
 class LayerView::Compositor::Natives : public mozilla::jni::NativeImpl<Compositor, Impl>
 {
 public:
-    static const JNINativeMethod methods[7];
+    static const JNINativeMethod methods[14];
 };
 
 template<class Impl>
 const JNINativeMethod LayerView::Compositor::Natives<Impl>::methods[] = {
 
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::AttachToJava_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::AttachToJava_t, Impl>
             ::template Wrap<&Impl::AttachToJava>),
@@ -364,20 +364,48 @@ const JNINativeMethod LayerView::Composi
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::CreateCompositor_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::CreateCompositor_t, Impl>
             ::template Wrap<&Impl::CreateCompositor>),
 
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::DisposeNative_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::DisposeNative_t, Impl>
             ::template Wrap<&Impl::DisposeNative>),
 
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::EnableLayerUpdateNotifications_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::EnableLayerUpdateNotifications_t, Impl>
+            ::template Wrap<&Impl::EnableLayerUpdateNotifications>),
+
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::OnSizeChanged_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::OnSizeChanged_t, Impl>
             ::template Wrap<&Impl::OnSizeChanged>),
 
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::RequestScreenPixels_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::RequestScreenPixels_t, Impl>
+            ::template Wrap<&Impl::RequestScreenPixels>),
+
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::SendToolbarAnimatorMessage_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::SendToolbarAnimatorMessage_t, Impl>
+            ::template Wrap<&Impl::SendToolbarAnimatorMessage>),
+
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::SendToolbarPixelsToCompositor_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::SendToolbarPixelsToCompositor_t, Impl>
+            ::template Wrap<&Impl::SendToolbarPixelsToCompositor>),
+
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::SetDefaultClearColor_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::SetDefaultClearColor_t, Impl>
+            ::template Wrap<&Impl::SetDefaultClearColor>),
+
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::SetMaxToolbarHeight_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::SetMaxToolbarHeight_t, Impl>
+            ::template Wrap<&Impl::SetMaxToolbarHeight>),
+
+    mozilla::jni::MakeNativeMethod<LayerView::Compositor::SetPinned_t>(
+            mozilla::jni::NativeStub<LayerView::Compositor::SetPinned_t, Impl>
+            ::template Wrap<&Impl::SetPinned>),
+
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::SyncInvalidateAndScheduleComposite_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::SyncInvalidateAndScheduleComposite_t, Impl>
             ::template Wrap<&Impl::SyncInvalidateAndScheduleComposite>),
 
     mozilla::jni::MakeNativeMethod<LayerView::Compositor::SyncPauseCompositor_t>(
             mozilla::jni::NativeStub<LayerView::Compositor::SyncPauseCompositor_t, Impl>
             ::template Wrap<&Impl::SyncPauseCompositor>),
 
@@ -385,38 +413,30 @@ const JNINativeMethod LayerView::Composi
             mozilla::jni::NativeStub<LayerView::Compositor::SyncResumeResizeCompositor_t, Impl>
             ::template Wrap<&Impl::SyncResumeResizeCompositor>)
 };
 
 template<class Impl>
 class NativePanZoomController::Natives : public mozilla::jni::NativeImpl<NativePanZoomController, Impl>
 {
 public:
-    static const JNINativeMethod methods[7];
+    static const JNINativeMethod methods[5];
 };
 
 template<class Impl>
 const JNINativeMethod NativePanZoomController::Natives<Impl>::methods[] = {
 
-    mozilla::jni::MakeNativeMethod<NativePanZoomController::AdjustScrollForSurfaceShift_t>(
-            mozilla::jni::NativeStub<NativePanZoomController::AdjustScrollForSurfaceShift_t, Impl>
-            ::template Wrap<&Impl::AdjustScrollForSurfaceShift>),
-
     mozilla::jni::MakeNativeMethod<NativePanZoomController::DisposeNative_t>(
             mozilla::jni::NativeStub<NativePanZoomController::DisposeNative_t, Impl>
             ::template Wrap<&Impl::DisposeNative>),
 
     mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMotionEvent_t>(
             mozilla::jni::NativeStub<NativePanZoomController::HandleMotionEvent_t, Impl>
             ::template Wrap<&Impl::HandleMotionEvent>),
 
-    mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMotionEventVelocity_t>(
-            mozilla::jni::NativeStub<NativePanZoomController::HandleMotionEventVelocity_t, Impl>
-            ::template Wrap<&Impl::HandleMotionEventVelocity>),
-
     mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleMouseEvent_t>(
             mozilla::jni::NativeStub<NativePanZoomController::HandleMouseEvent_t, Impl>
             ::template Wrap<&Impl::HandleMouseEvent>),
 
     mozilla::jni::MakeNativeMethod<NativePanZoomController::HandleScrollEvent_t>(
             mozilla::jni::NativeStub<NativePanZoomController::HandleScrollEvent_t, Impl>
             ::template Wrap<&Impl::HandleScrollEvent>),
 
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1080,137 +1080,78 @@ const char GeckoLayerClient::name[] =
 constexpr char GeckoLayerClient::ContentDocumentChanged_t::name[];
 constexpr char GeckoLayerClient::ContentDocumentChanged_t::signature[];
 
 auto GeckoLayerClient::ContentDocumentChanged() const -> void
 {
     return mozilla::jni::Method<ContentDocumentChanged_t>::Call(GeckoLayerClient::mCtx, nullptr);
 }
 
-constexpr char GeckoLayerClient::CreateFrame_t::name[];
-constexpr char GeckoLayerClient::CreateFrame_t::signature[];
-
-auto GeckoLayerClient::CreateFrame() const -> mozilla::jni::Object::LocalRef
-{
-    return mozilla::jni::Method<CreateFrame_t>::Call(GeckoLayerClient::mCtx, nullptr);
-}
-
 constexpr char GeckoLayerClient::IsContentDocumentDisplayed_t::name[];
 constexpr char GeckoLayerClient::IsContentDocumentDisplayed_t::signature[];
 
 auto GeckoLayerClient::IsContentDocumentDisplayed() const -> bool
 {
     return mozilla::jni::Method<IsContentDocumentDisplayed_t>::Call(GeckoLayerClient::mCtx, nullptr);
 }
 
 constexpr char GeckoLayerClient::OnGeckoReady_t::name[];
 constexpr char GeckoLayerClient::OnGeckoReady_t::signature[];
 
 auto GeckoLayerClient::OnGeckoReady() const -> void
 {
     return mozilla::jni::Method<OnGeckoReady_t>::Call(GeckoLayerClient::mCtx, nullptr);
 }
 
-constexpr char GeckoLayerClient::SetFirstPaintViewport_t::name[];
-constexpr char GeckoLayerClient::SetFirstPaintViewport_t::signature[];
-
-auto GeckoLayerClient::SetFirstPaintViewport(float a0, float a1, float a2, float a3, float a4, float a5, float a6) const -> void
-{
-    return mozilla::jni::Method<SetFirstPaintViewport_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6);
-}
-
-constexpr char GeckoLayerClient::SyncFrameMetrics_t::name[];
-constexpr char GeckoLayerClient::SyncFrameMetrics_t::signature[];
-
-auto GeckoLayerClient::SyncFrameMetrics(float a0, float a1, float a2, float a3, float a4, float a5, float a6, int32_t a7, int32_t a8, int32_t a9, int32_t a10, float a11, bool a12, int32_t a13) const -> mozilla::jni::Object::LocalRef
-{
-    return mozilla::jni::Method<SyncFrameMetrics_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
-}
-
 constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::name[];
 constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::signature[];
 
 auto GeckoLayerClient::SynthesizeNativeMouseEvent(int32_t a0, int32_t a1, int32_t a2) const -> void
 {
     return mozilla::jni::Method<SynthesizeNativeMouseEvent_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2);
 }