merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 19 Jan 2016 12:00:45 +0100
changeset 317457 b67316254602a63bf4e568198a5c7d3288a9db27
parent 317395 c4e56cd70b034e2c402ca9a4c180598fa0e8277f (current diff)
parent 317456 20fdd2a2344e129897217b42468cc5c85dc3f76d (diff)
child 317458 2e50b83954e62d52d2ef294e850c4380d457d96a
child 317462 88cb02abee76f781ca1cee7e799897dd74f95919
child 317496 5f1246a49f86d34747efac27d9d1c65c52b95169
child 317543 6043c53cc03e82f71d6a1e1be792b985f1e7a030
push id1079
push userjlund@mozilla.com
push dateFri, 15 Apr 2016 21:02:33 +0000
treeherdermozilla-release@575fbf6786d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone46.0a1
first release with
nightly linux32
b67316254602 / 46.0a1 / 20160119030232 / files
nightly linux64
b67316254602 / 46.0a1 / 20160119030232 / files
nightly mac
b67316254602 / 46.0a1 / 20160119030232 / files
nightly win32
b67316254602 / 46.0a1 / 20160119030232 / files
nightly win64
b67316254602 / 46.0a1 / 20160119030232 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp
mobile/android/app/mobile.js
--- a/accessible/tests/mochitest/text/test_lineboundary.html
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -231,25 +231,25 @@ two words
   <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
 
   <p id="ht_4">Hello world
 </p>
 
   <ul id="ul1">
     <li id="li1">Item</li>
     <li id="li2"></li>
-    <li id="li3" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li>
+    <li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
     <li id="li4">a <a href=''>b</a> c</li>
     <li id="li5"><br>hello</li>
   </ul>
 
   <ol id="ol1">
     <li id="li6">Item</li>
     <li id="li7"></li>
-    <li id="li8" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li>
+    <li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
     <li id="li9">a <a href=''>b</a> c</li>
     <li id="li10"><br>hello</li>
   </ol>
 
   <div id="ht_5">
     <div>
       <p>sectiounus</p>
       <p>seciofarus</p>
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -396,22 +396,44 @@ nsScriptSecurityManager::GetChannelURIPr
     // as nsDocument::Reset and XULDocument::StartDocumentLoad.
     nsCOMPtr<nsIURI> uri;
     nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsILoadContext> loadContext;
     NS_QueryNotificationCallbacks(aChannel, loadContext);
 
-    if (loadContext) {
-        return GetLoadContextCodebasePrincipal(uri, loadContext, aPrincipal);
+    nsCOMPtr<nsILoadInfo> loadInfo;
+    aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+    nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INVALID;
+    if (loadInfo) {
+      contentPolicyType = loadInfo->GetExternalContentPolicyType();
     }
 
-    //TODO: Bug 1211590. inherit Origin Attributes from LoadInfo.
-    PrincipalOriginAttributes attrs(UNKNOWN_APP_ID, false);
+    PrincipalOriginAttributes attrs;
+    if (nsIContentPolicy::TYPE_DOCUMENT == contentPolicyType ||
+        nsIContentPolicy::TYPE_SUBDOCUMENT == contentPolicyType) {
+      // If it's document or sub-document, inherit originAttributes from
+      // the document.
+      if (loadContext) {
+        DocShellOriginAttributes docShellAttrs;
+        loadContext->GetOriginAttributes(docShellAttrs);
+        attrs.InheritFromDocShellToDoc(docShellAttrs, uri);
+      }
+    } else {
+      // Inherit origin attributes from loading principal if any.
+      nsCOMPtr<nsIPrincipal> loadingPrincipal;
+      if (loadInfo) {
+        loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
+      }
+      if (loadingPrincipal) {
+        attrs = BasePrincipal::Cast(loadingPrincipal)->OriginAttributesRef();
+      }
+    }
+
     rv = MaybeSetAddonIdFromURI(attrs, uri);
     NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
     prin.forget(aPrincipal);
     return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4006,16 +4006,17 @@ nsDocShell::AddChild(nsIDocShellTreeItem
     childDocShell->SetUseGlobalHistory(true);
   }
 
   if (aChild->ItemType() != mItemType) {
     return NS_OK;
   }
 
   aChild->SetTreeOwner(mTreeOwner);
+  childDocShell->SetUserContextId(mUserContextId);
 
   nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
   if (!childAsDocShell) {
     return NS_OK;
   }
 
   // charset, style-disabling, and zoom will be inherited in SetupNewViewer()
 
@@ -13853,16 +13854,27 @@ nsDocShell::SetIsSignedPackage(const nsA
   mSignedPkg = aSignedPkg;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetUserContextId(uint32_t aUserContextId)
 {
   mUserContextId = aUserContextId;
+
+  nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
+  while (iter.HasMore()) {
+    nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
+    if (!docshell || docshell->ItemType() != ItemType()) {
+      continue;
+    }
+
+    docshell->SetUserContextId(aUserContextId);
+  }
+
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetIsBrowserElement(bool* aIsBrowser)
 {
   *aIsBrowser = (mFrameType == eFrameTypeBrowser);
   return NS_OK;
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -791,47 +791,38 @@ Animation::ComposeStyle(RefPtr<AnimValue
   //     that case the compositor might have been ahead of the main thread so we
   //     should use the current wallclock time to ensure the animation doesn't
   //     temporarily jump backwards.
   //
   // To address each of these cases we temporarily tweak the hold time
   // immediately before updating the style rule and then restore it immediately
   // afterwards. This is purely to prevent visual flicker. Other behavior
   // such as dispatching events continues to rely on the regular timeline time.
-  bool updatedHoldTime = false;
   AnimationPlayState playState = PlayState();
   {
     AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
 
     if (playState == AnimationPlayState::Pending &&
         mHoldTime.IsNull() &&
         !mStartTime.IsNull()) {
       Nullable<TimeDuration> timeToUse = mPendingReadyTime;
       if (timeToUse.IsNull() &&
           mTimeline &&
           mTimeline->TracksWallclockTime()) {
         timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
       }
       if (!timeToUse.IsNull()) {
         mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
                             .MultDouble(mPlaybackRate));
-        // Push the change down to the effect
-        UpdateEffect();
-        updatedHoldTime = true;
       }
     }
 
     mEffect->ComposeStyle(aStyleRule, aSetProperties);
   }
 
-  // Now that the hold time has been restored, update the effect
-  if (updatedHoldTime) {
-    UpdateEffect();
-  }
-
   MOZ_ASSERT(playState == PlayState(),
              "Play state should not change during the course of compositing");
   mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
 }
 
 void
 Animation::NotifyEffectTimingUpdated()
 {
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -560,16 +560,19 @@ EffectCompositor::ComposeAnimationRule(d
   // As a result, we iterate from last animation to first and, if a
   // property has already been set, we don't change it.
   nsCSSPropertySet properties;
 
   for (KeyframeEffectReadOnly* effect : Reversed(sortedEffectList)) {
     effect->GetAnimation()->ComposeStyle(animationRule, properties);
   }
 
+  MOZ_ASSERT(effects == EffectSet::GetEffectSet(aElement, aPseudoType),
+             "EffectSet should not change while composing style");
+
   effects->UpdateAnimationRuleRefreshTime(aCascadeLevel, aRefreshTime);
 }
 
 /* static */ void
 EffectCompositor::GetOverriddenProperties(nsStyleContext* aStyleContext,
                                           EffectSet& aEffectSet,
                                           nsCSSPropertySet&
                                             aPropertiesOverridden)
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1775,18 +1775,17 @@ Element::UnbindFromTree(bool aDeep, bool
   }
 
   // Ensure that CSS transitions don't continue on an element at a
   // different place in the tree (even if reinserted before next
   // animation refresh).
   // We need to delete the properties while we're still in document
   // (if we were in document).
   // FIXME (Bug 522599): Need a test for this.
-  //XXXsmaug this looks slow.
-  if (HasFlag(NODE_HAS_PROPERTIES)) {
+  if (MayHaveAnimations()) {
     DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
     DeleteProperty(nsGkAtoms::transitionsProperty);
     DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
     DeleteProperty(nsGkAtoms::animationsProperty);
   }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8238,16 +8238,34 @@ nsContentUtils::InternalStorageAllowedFo
     }
 
     // Check if we are in private browsing, and record that fact
     if (IsInPrivateBrowsing(document)) {
       access = StorageAccess::ePrivateBrowsing;
     }
   }
 
+  nsCOMPtr<nsIPermissionManager> permissionManager =
+    services::GetPermissionManager();
+  if (!permissionManager) {
+    return StorageAccess::eDeny;
+  }
+
+  // check the permission manager for any allow or deny permissions
+  // for cookies for the window.
+  uint32_t perm;
+  permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
+  if (perm == nsIPermissionManager::DENY_ACTION) {
+    return StorageAccess::eDeny;
+  } else if (perm == nsICookiePermission::ACCESS_SESSION) {
+    return std::min(access, StorageAccess::eSessionScoped);
+  } else if (perm == nsIPermissionManager::ALLOW_ACTION) {
+    return access;
+  }
+
   // Check if we should only allow storage for the session, and record that fact
   if (sCookiesLifetimePolicy == nsICookieService::ACCEPT_SESSION) {
     // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
     // so perform a std::min comparison to make sure we preserve ePrivateBrowsing
     // if it has been set.
     access = std::min(StorageAccess::eSessionScoped, access);
   }
 
@@ -8279,34 +8297,16 @@ nsContentUtils::InternalStorageAllowedFo
   if (NS_SUCCEEDED(rv) && uri) {
     bool isAbout = false;
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)));
     if (isAbout) {
       return access;
     }
   }
 
-  nsCOMPtr<nsIPermissionManager> permissionManager =
-    services::GetPermissionManager();
-  if (!permissionManager) {
-    return StorageAccess::eDeny;
-  }
-
-  // check the permission manager for any allow or deny permissions
-  // for cookies for the window.
-  uint32_t perm;
-  permissionManager->TestPermissionFromPrincipal(aPrincipal, "cookie", &perm);
-  if (perm == nsIPermissionManager::DENY_ACTION) {
-    return StorageAccess::eDeny;
-  } else if (perm == nsICookiePermission::ACCESS_SESSION) {
-    return std::min(access, StorageAccess::eSessionScoped);
-  } else if (perm == nsIPermissionManager::ALLOW_ACTION) {
-    return access;
-  }
-
   // We don't want to prompt for every attempt to access permissions.
   if (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT) {
     return StorageAccess::eDeny;
   }
 
   // In the absense of a window, we assume that we are first-party.
   if (aWindow && (sCookiesBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
                   sCookiesBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN)) {
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1217,16 +1217,17 @@ GK_ATOM(top, "top")
 GK_ATOM(topleft, "topleft")
 GK_ATOM(topmargin, "topmargin")
 GK_ATOM(toppadding, "toppadding")
 GK_ATOM(topright, "topright")
 GK_ATOM(tr, "tr")
 GK_ATOM(track, "track")
 GK_ATOM(trailing, "trailing")
 GK_ATOM(transform, "transform")
+GK_ATOM(transform_3d, "transform-3d")
 GK_ATOM(transformiix, "transformiix")
 GK_ATOM(translate, "translate")
 GK_ATOM(transparent, "transparent")
 GK_ATOM(tree, "tree")
 GK_ATOM(treecell, "treecell")
 GK_ATOM(treechildren, "treechildren")
 GK_ATOM(treecol, "treecol")
 GK_ATOM(treecolpicker, "treecolpicker")
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -524,17 +524,17 @@ nsDOMCameraControl::GetCameraStream() co
   return mInput;
 }
 
 void
 nsDOMCameraControl::TrackCreated(TrackID aTrackID) {
   // This track is not connected through a port.
   MediaInputPort* inputPort = nullptr;
   dom::VideoStreamTrack* track =
-    new dom::VideoStreamTrack(this, aTrackID);
+    new dom::VideoStreamTrack(this, aTrackID, nsString());
   RefPtr<TrackPort> port =
     new TrackPort(inputPort, track,
                   TrackPort::InputPortOwnership::OWNED);
   mTracks.AppendElement(port.forget());
   NotifyTrackAdded(track);
 }
 
 #define THROW_IF_NO_CAMERACONTROL(...)                                          \
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -677,17 +677,17 @@ HTMLCanvasElement::CaptureStream(const O
 
   TrackID videoTrackId = 1;
   nsresult rv = stream->Init(aFrameRate, videoTrackId);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
-  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
+  stream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
 
   rv = RegisterFrameCaptureListener(stream->FrameCaptureListener());
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   return stream.forget();
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1885,21 +1885,21 @@ HTMLMediaElement::CaptureStreamInternal(
   mAudioCaptured = true;
   if (mDecoder) {
     mDecoder->AddOutputStream(out->mStream->GetInputStream()->AsProcessedStream(),
                               aFinishWhenEnded);
     if (mReadyState >= HAVE_METADATA) {
       // Expose the tracks to JS directly.
       if (HasAudio()) {
         TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
-        out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO);
+        out->mStream->CreateOwnDOMTrack(audioTrackId, MediaSegment::AUDIO, nsString());
       }
       if (HasVideo()) {
         TrackID videoTrackId = mMediaInfo.mVideo.mTrackId;
-        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO);
+        out->mStream->CreateOwnDOMTrack(videoTrackId, MediaSegment::VIDEO, nsString());
       }
     }
   }
   RefPtr<DOMMediaStream> result = out->mStream;
   return result.forget();
 }
 
 already_AddRefed<DOMMediaStream>
--- a/dom/media/AudioStreamTrack.h
+++ b/dom/media/AudioStreamTrack.h
@@ -9,18 +9,18 @@
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioStreamTrack : public MediaStreamTrack {
 public:
-  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
-    : MediaStreamTrack(aStream, aTrackID) {}
+  AudioStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
+    : MediaStreamTrack(aStream, aTrackID, aLabel) {}
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual AudioStreamTrack* AsAudioStreamTrack() override { return this; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); }
 };
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -123,17 +123,17 @@ public:
     if (track) {
       // This track has already been manually created. Abort.
       return;
     }
 
     NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(),
                      "A new track was detected on the input stream; creating a corresponding MediaStreamTrack. "
                      "Initial tracks should be added manually to immediately and synchronously be available to JS.");
-    mStream->CreateOwnDOMTrack(aTrackId, aType);
+    mStream->CreateOwnDOMTrack(aTrackId, aType, nsString());
   }
 
   void DoNotifyTrackEnded(TrackID aTrackId)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mStream) {
       return;
@@ -614,17 +614,17 @@ DOMMediaStream::InitAudioCaptureStream(n
 {
   mWindow = aWindow;
 
   const TrackID AUDIO_TRACK = 1;
 
   InitInputStreamCommon(aGraph->CreateAudioCaptureStream(this, AUDIO_TRACK), aGraph);
   InitOwnedStreamCommon(aGraph);
   InitPlaybackStreamCommon(aGraph);
-  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO);
+  CreateOwnDOMTrack(AUDIO_TRACK, MediaSegment::AUDIO, nsString());
 }
 
 void
 DOMMediaStream::InitInputStreamCommon(MediaStream* aStream,
                                       MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(!mOwnedStream, "Input stream must be initialized before owned stream");
 
@@ -775,30 +775,30 @@ DOMMediaStream::AddPrincipalChangeObserv
 
 bool
 DOMMediaStream::RemovePrincipalChangeObserver(PrincipalChangeObserver* aObserver)
 {
   return mPrincipalChangeObservers.RemoveElement(aObserver);
 }
 
 MediaStreamTrack*
-DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
+DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel)
 {
   MOZ_RELEASE_ASSERT(mInputStream);
   MOZ_RELEASE_ASSERT(mOwnedStream);
 
   MOZ_ASSERT(FindOwnedDOMTrack(GetOwnedStream(), aTrackID) == nullptr);
 
   MediaStreamTrack* track;
   switch (aType) {
   case MediaSegment::AUDIO:
-    track = new AudioStreamTrack(this, aTrackID);
+    track = new AudioStreamTrack(this, aTrackID, aLabel);
     break;
   case MediaSegment::VIDEO:
-    track = new VideoStreamTrack(this, aTrackID);
+    track = new VideoStreamTrack(this, aTrackID, aLabel);
     break;
   default:
     MOZ_CRASH("Unhandled track type");
   }
 
   LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID));
 
   RefPtr<TrackPort> ownedTrackPort =
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -471,17 +471,17 @@ public:
   }
 
   /**
    * Called for each track in our owned stream to indicate to JS that we
    * are carrying that track.
    *
    * Creates a MediaStreamTrack, adds it to mTracks and returns it.
    */
-  MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
+  MediaStreamTrack* CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType, const nsString& aLabel);
 
   class OnTracksAvailableCallback {
   public:
     virtual ~OnTracksAvailableCallback() {}
     virtual void NotifyTracksAvailable(DOMMediaStream* aStream) = 0;
   };
   // When the initial set of tracks has been added, run
   // aCallback->NotifyTracksAvailable.
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1020,20 +1020,24 @@ public:
     } else {
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking
       domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener,
                                                            mAudioDevice, mVideoDevice,
                                                            msg);
 
       if (mAudioDevice) {
-        domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO);
+        nsString audioDeviceName;
+        mAudioDevice->GetName(audioDeviceName);
+        domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO, audioDeviceName);
       }
       if (mVideoDevice) {
-        domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO);
+        nsString videoDeviceName;
+        mVideoDevice->GetName(videoDeviceName);
+        domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO, videoDeviceName);
       }
 
       nsCOMPtr<nsIPrincipal> principal;
       if (mPeerIdentity) {
         principal = nsNullPrincipal::Create();
         domStream->SetPeerIdentity(mPeerIdentity.forget());
       } else {
         principal = window->GetExtantDoc()->NodePrincipal();
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -7,18 +7,18 @@
 
 #include "DOMMediaStream.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 namespace mozilla {
 namespace dom {
 
-MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
-  : mOwningStream(aStream), mTrackID(aTrackID), mEnded(false), mEnabled(true)
+MediaStreamTrack::MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
+  : mOwningStream(aStream), mTrackID(aTrackID), mLabel(aLabel), mEnded(false), mEnabled(true)
 {
 
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidgen =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
 
   nsID uuid;
   memset(&uuid, 0, sizeof(uuid));
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -24,17 +24,17 @@ class VideoStreamTrack;
  * Class representing a track in a DOMMediaStream.
  */
 class MediaStreamTrack : public DOMEventTargetHelper {
 public:
   /**
    * aTrackID is the MediaStreamGraph track ID for the track in the
    * MediaStream owned by aStream.
    */
-  MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID);
+  MediaStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrack,
                                            DOMEventTargetHelper)
 
   DOMMediaStream* GetParentObject() const { return mOwningStream; }
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override = 0;
 
@@ -49,17 +49,17 @@ public:
    */
   TrackID GetTrackID() const { return mTrackID; }
   virtual AudioStreamTrack* AsAudioStreamTrack() { return nullptr; }
   virtual VideoStreamTrack* AsVideoStreamTrack() { return nullptr; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) = 0;
   void GetId(nsAString& aID) const;
-  void GetLabel(nsAString& aLabel) { aLabel.Truncate(); }
+  void GetLabel(nsAString& aLabel) { aLabel.Assign(mLabel); }
   bool Enabled() { return mEnabled; }
   void SetEnabled(bool aEnabled);
   void Stop();
   already_AddRefed<Promise>
   ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
 
   bool Ended() const { return mEnded; }
   // Notifications from the MediaStreamGraph
@@ -70,16 +70,17 @@ public:
   void AssignId(const nsAString& aID) { mID = aID; }
 
 protected:
   virtual ~MediaStreamTrack();
 
   RefPtr<DOMMediaStream> mOwningStream;
   TrackID mTrackID;
   nsString mID;
+  nsString mLabel;
   bool mEnded;
   bool mEnabled;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* MEDIASTREAMTRACK_H_ */
--- a/dom/media/VideoStreamTrack.h
+++ b/dom/media/VideoStreamTrack.h
@@ -9,18 +9,18 @@
 #include "MediaStreamTrack.h"
 #include "DOMMediaStream.h"
 
 namespace mozilla {
 namespace dom {
 
 class VideoStreamTrack : public MediaStreamTrack {
 public:
-  VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID)
-    : MediaStreamTrack(aStream, aTrackID) {}
+  VideoStreamTrack(DOMMediaStream* aStream, TrackID aTrackID, const nsString& aLabel)
+    : MediaStreamTrack(aStream, aTrackID, aLabel) {}
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   virtual VideoStreamTrack* AsVideoStreamTrack() override { return this; }
 
   // WebIDL
   virtual void GetKind(nsAString& aKind) override { aKind.AssignLiteral("video"); }
 };
--- a/dom/media/systemservices/CamerasChild.cpp
+++ b/dom/media/systemservices/CamerasChild.cpp
@@ -24,84 +24,26 @@
 #undef LOG_ENABLED
 mozilla::LazyLogModule gCamerasChildLog("CamerasChild");
 #define LOG(args) MOZ_LOG(gCamerasChildLog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gCamerasChildLog, mozilla::LogLevel::Debug)
 
 namespace mozilla {
 namespace camera {
 
-// We emulate the sync webrtc.org API with the help of singleton
-// CamerasSingleton, which manages a pointer to an IPC object, a thread
-// where IPC operations should run on, and a mutex.
-// The static function Cameras() will use that Singleton to set up,
-// if needed, both the thread and the associated IPC objects and return
-// a pointer to the IPC object. Users can then do IPC calls on that object
-// after dispatching them to aforementioned thread.
-
-// 2 Threads are involved in this code:
-// - the MediaManager thread, which will call the (static, sync API) functions
-//   through MediaEngineRemoteVideoSource
-// - the Cameras IPC thread, which will be doing our IPC to the parent process
-//   via PBackground
-
-// Our main complication is that we emulate a sync API while (having to do)
-// async messaging. We dispatch the messages to another thread to send them
-// async and hold a Monitor to wait for the result to be asynchronously received
-// again. The requirement for async messaging originates on the parent side:
-// it's not reasonable to block all PBackground IPC there while waiting for
-// something like device enumeration to complete.
-
-class CamerasSingleton {
-public:
-  CamerasSingleton()
-    : mCamerasMutex("CamerasSingleton::mCamerasMutex"),
-      mCameras(nullptr),
-      mCamerasChildThread(nullptr) {
-    LOG(("CamerasSingleton: %p", this));
-  }
-
-  ~CamerasSingleton() {
-    LOG(("~CamerasSingleton: %p", this));
-  }
+CamerasSingleton::CamerasSingleton()
+  : mCamerasMutex("CamerasSingleton::mCamerasMutex"),
+    mCameras(nullptr),
+    mCamerasChildThread(nullptr) {
+  LOG(("CamerasSingleton: %p", this));
+}
 
-  static CamerasSingleton& GetInstance() {
-    static CamerasSingleton instance;
-    return instance;
-  }
-
-  static OffTheBooksMutex& Mutex() {
-    return GetInstance().mCamerasMutex;
-  }
-
-  static CamerasChild*& Child() {
-    GetInstance().Mutex().AssertCurrentThreadOwns();
-    return GetInstance().mCameras;
-  }
-
-  static nsCOMPtr<nsIThread>& Thread() {
-    GetInstance().Mutex().AssertCurrentThreadOwns();
-    return GetInstance().mCamerasChildThread;
-  }
-
-private:
-  // Reinitializing CamerasChild will change the pointers below.
-  // We don't want this to happen in the middle of preparing IPC.
-  // We will be alive on destruction, so this needs to be off the books.
-  mozilla::OffTheBooksMutex mCamerasMutex;
-
-  // This is owned by the IPC code, and the same code controls the lifetime.
-  // It will set and clear this pointer as appropriate in setup/teardown.
-  // We'd normally make this a WeakPtr but unfortunately the IPC code already
-  // uses the WeakPtr mixin in a protected base class of CamerasChild, and in
-  // any case the object becomes unusable as soon as IPC is tearing down, which
-  // will be before actual destruction.
-  CamerasChild* mCameras;
-  nsCOMPtr<nsIThread> mCamerasChildThread;
-};
+CamerasSingleton::~CamerasSingleton() {
+  LOG(("~CamerasSingleton: %p", this));
+}
 
 class InitializeIPCThread : public nsRunnable
 {
 public:
   InitializeIPCThread()
     : mCamerasChild(nullptr) {}
 
   NS_IMETHOD Run() override {
@@ -130,17 +72,17 @@ public:
   CamerasChild* GetCamerasChild() {
     return mCamerasChild;
   }
 
 private:
   CamerasChild* mCamerasChild;
 };
 
-static CamerasChild*
+CamerasChild*
 GetCamerasChild() {
   CamerasSingleton::Mutex().AssertCurrentThreadOwns();
   if (!CamerasSingleton::Child()) {
     MOZ_ASSERT(!NS_IsMainThread(), "Should not be on the main Thread");
     MOZ_ASSERT(!CamerasSingleton::Thread());
     LOG(("No sCameras, setting up IPC Thread"));
     nsresult rv = NS_NewNamedThread("Cameras IPC",
                                     getter_AddRefs(CamerasSingleton::Thread()));
@@ -184,27 +126,16 @@ CamerasChild::RecvReplySuccess(void)
   LOG((__PRETTY_FUNCTION__));
   MonitorAutoLock monitor(mReplyMonitor);
   mReceivedReply = true;
   mReplySuccess = true;
   monitor.Notify();
   return true;
 }
 
-int NumberOfCapabilities(CaptureEngine aCapEngine, const char* deviceUniqueIdUTF8)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->NumberOfCapabilities(aCapEngine, deviceUniqueIdUTF8);
-  } else {
-    return 0;
-  }
-}
-
 bool
 CamerasChild::RecvReplyNumberOfCapabilities(const int& numdev)
 {
   LOG((__PRETTY_FUNCTION__));
   MonitorAutoLock monitor(mReplyMonitor);
   mReceivedReply = true;
   mReplySuccess = true;
   mReplyInteger = numdev;
@@ -258,27 +189,16 @@ CamerasChild::NumberOfCapabilities(Captu
   if (!DispatchToParent(runnable, monitor)) {
     LOG(("Get capture capability count failed"));
     return 0;
   }
   LOG(("Capture capability count: %d", mReplyInteger));
   return mReplyInteger;
 }
 
-int NumberOfCaptureDevices(CaptureEngine aCapEngine)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->NumberOfCaptureDevices(aCapEngine);
-  } else {
-    return 0;
-  }
-}
-
 int
 CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG((__PRETTY_FUNCTION__));
   nsCOMPtr<nsIRunnable> runnable =
     media::NewRunnableFrom([this, aCapEngine]() -> nsresult {
       if (this->SendNumberOfCaptureDevices(aCapEngine)) {
@@ -302,32 +222,16 @@ CamerasChild::RecvReplyNumberOfCaptureDe
   MonitorAutoLock monitor(mReplyMonitor);
   mReceivedReply = true;
   mReplySuccess = true;
   mReplyInteger = numdev;
   monitor.Notify();
   return true;
 }
 
-int GetCaptureCapability(CaptureEngine aCapEngine, const char* unique_idUTF8,
-                         const unsigned int capability_number,
-                         webrtc::CaptureCapability& capability)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->GetCaptureCapability(aCapEngine,
-                                       unique_idUTF8,
-                                       capability_number,
-                                       capability);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine,
                                    const char* unique_idUTF8,
                                    const unsigned int capability_number,
                                    webrtc::CaptureCapability& capability)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number));
@@ -360,37 +264,16 @@ CamerasChild::RecvReplyGetCaptureCapabil
   mReplyCapability.expectedCaptureDelay = ipcCapability.expectedCaptureDelay();
   mReplyCapability.rawType = static_cast<webrtc::RawVideoType>(ipcCapability.rawType());
   mReplyCapability.codecType = static_cast<webrtc::VideoCodecType>(ipcCapability.codecType());
   mReplyCapability.interlaced = ipcCapability.interlaced();
   monitor.Notify();
   return true;
 }
 
-
-int GetCaptureDevice(CaptureEngine aCapEngine,
-                     unsigned int list_number, char* device_nameUTF8,
-                     const unsigned int device_nameUTF8Length,
-                     char* unique_idUTF8,
-                     const unsigned int unique_idUTF8Length)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->GetCaptureDevice(aCapEngine,
-                                   list_number,
-                                   device_nameUTF8,
-                                   device_nameUTF8Length,
-                                   unique_idUTF8,
-                                   unique_idUTF8Length);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine,
                                unsigned int list_number, char* device_nameUTF8,
                                const unsigned int device_nameUTF8Length,
                                char* unique_idUTF8,
                                const unsigned int unique_idUTF8Length)
 {
   MutexAutoLock requestLock(mRequestMutex);
@@ -422,33 +305,16 @@ CamerasChild::RecvReplyGetCaptureDevice(
   mReceivedReply = true;
   mReplySuccess = true;
   mReplyDeviceName = device_name;
   mReplyDeviceID = device_id;
   monitor.Notify();
   return true;
 }
 
-int AllocateCaptureDevice(CaptureEngine aCapEngine,
-                          const char* unique_idUTF8,
-                          const unsigned int unique_idUTF8Length,
-                          int& capture_id)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->AllocateCaptureDevice(aCapEngine,
-                                        unique_idUTF8,
-                                        unique_idUTF8Length,
-                                        capture_id);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine,
                                     const char* unique_idUTF8,
                                     const unsigned int unique_idUTF8Length,
                                     int& capture_id)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG((__PRETTY_FUNCTION__));
@@ -478,27 +344,16 @@ CamerasChild::RecvReplyAllocateCaptureDe
   MonitorAutoLock monitor(mReplyMonitor);
   mReceivedReply = true;
   mReplySuccess = true;
   mReplyInteger = numdev;
   monitor.Notify();
   return true;
 }
 
-int ReleaseCaptureDevice(CaptureEngine aCapEngine, const int capture_id)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->ReleaseCaptureDevice(aCapEngine, capture_id);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine,
                                    const int capture_id)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG((__PRETTY_FUNCTION__));
   nsCOMPtr<nsIRunnable> runnable =
     media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult {
@@ -534,33 +389,16 @@ CamerasChild::RemoveCallback(const Captu
     CapturerElement ce = mCallbacks[i];
     if (ce.engine == aCapEngine && ce.id == capture_id) {
       mCallbacks.RemoveElementAt(i);
       break;
     }
   }
 }
 
-int StartCapture(CaptureEngine aCapEngine,
-                 const int capture_id,
-                 webrtc::CaptureCapability& webrtcCaps,
-                 webrtc::ExternalRenderer* cb)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->StartCapture(aCapEngine,
-                               capture_id,
-                               webrtcCaps,
-                               cb);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::StartCapture(CaptureEngine aCapEngine,
                            const int capture_id,
                            webrtc::CaptureCapability& webrtcCaps,
                            webrtc::ExternalRenderer* cb)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG((__PRETTY_FUNCTION__));
@@ -581,27 +419,16 @@ CamerasChild::StartCapture(CaptureEngine
     });
   MonitorAutoLock monitor(mReplyMonitor);
   if (!DispatchToParent(runnable, monitor)) {
     return -1;
   }
   return 0;
 }
 
-int StopCapture(CaptureEngine aCapEngine, const int capture_id)
-{
-  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
-  CamerasChild* child = GetCamerasChild();
-  if (child) {
-    return child->StopCapture(aCapEngine, capture_id);
-  } else {
-    return -1;
-  }
-}
-
 int
 CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id)
 {
   MutexAutoLock requestLock(mRequestMutex);
   LOG((__PRETTY_FUNCTION__));
   nsCOMPtr<nsIRunnable> runnable =
     media::NewRunnableFrom([this, aCapEngine, capture_id]() -> nsresult {
       if (this->SendStopCapture(aCapEngine, capture_id)) {
--- a/dom/media/systemservices/CamerasChild.h
+++ b/dom/media/systemservices/CamerasChild.h
@@ -2,16 +2,17 @@
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* 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_CamerasChild_h
 #define mozilla_CamerasChild_h
 
+#include "mozilla/Move.h"
 #include "mozilla/Pair.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/camera/PCamerasChild.h"
 #include "mozilla/camera/PCamerasParent.h"
 #include "mozilla/Mutex.h"
 #include "nsCOMPtr.h"
 
 // conflicts with #include of scoped_ptr.h
@@ -41,43 +42,106 @@ enum CaptureEngine : int {
 };
 
 struct CapturerElement {
   CaptureEngine engine;
   int id;
   webrtc::ExternalRenderer* callback;
 };
 
-// statically mirror webrtc.org ViECapture API
-// these are called via MediaManager->MediaEngineRemoteVideoSource
-// on the MediaManager thread
-int NumberOfCapabilities(CaptureEngine aCapEngine,
-                         const char* deviceUniqueIdUTF8);
-int GetCaptureCapability(CaptureEngine aCapEngine,
-                         const char* unique_idUTF8,
-                         const unsigned int capability_number,
-                         webrtc::CaptureCapability& capability);
-int NumberOfCaptureDevices(CaptureEngine aCapEngine);
-int GetCaptureDevice(CaptureEngine aCapEngine,
-                     unsigned int list_number, char* device_nameUTF8,
-                     const unsigned int device_nameUTF8Length,
-                     char* unique_idUTF8,
-                     const unsigned int unique_idUTF8Length);
-int AllocateCaptureDevice(CaptureEngine aCapEngine,
-                          const char* unique_idUTF8,
-                          const unsigned int unique_idUTF8Length,
-                          int& capture_id);
-int ReleaseCaptureDevice(CaptureEngine aCapEngine,
-                         const int capture_id);
-int StartCapture(CaptureEngine aCapEngine,
-                 const int capture_id, webrtc::CaptureCapability& capability,
-                 webrtc::ExternalRenderer* func);
-int StopCapture(CaptureEngine aCapEngine, const int capture_id);
+// Forward declaration so we can work with pointers to it.
+class CamerasChild;
+
+// We emulate the sync webrtc.org API with the help of singleton
+// CamerasSingleton, which manages a pointer to an IPC object, a thread
+// where IPC operations should run on, and a mutex.
+// The static function Cameras() will use that Singleton to set up,
+// if needed, both the thread and the associated IPC objects and return
+// a pointer to the IPC object. Users can then do IPC calls on that object
+// after dispatching them to aforementioned thread.
+
+// 2 Threads are involved in this code:
+// - the MediaManager thread, which will call the (static, sync API) functions
+//   through MediaEngineRemoteVideoSource
+// - the Cameras IPC thread, which will be doing our IPC to the parent process
+//   via PBackground
+
+// Our main complication is that we emulate a sync API while (having to do)
+// async messaging. We dispatch the messages to another thread to send them
+// async and hold a Monitor to wait for the result to be asynchronously received
+// again. The requirement for async messaging originates on the parent side:
+// it's not reasonable to block all PBackground IPC there while waiting for
+// something like device enumeration to complete.
+
+class CamerasSingleton {
+public:
+  CamerasSingleton();
+  ~CamerasSingleton();
+
+  static OffTheBooksMutex& Mutex() {
+    return GetInstance().mCamerasMutex;
+  }
+
+  static CamerasChild*& Child() {
+    Mutex().AssertCurrentThreadOwns();
+    return GetInstance().mCameras;
+  }
+
+  static nsCOMPtr<nsIThread>& Thread() {
+    Mutex().AssertCurrentThreadOwns();
+    return GetInstance().mCamerasChildThread;
+  }
+
+private:
+  static CamerasSingleton& GetInstance() {
+    static CamerasSingleton instance;
+    return instance;
+  }
+
+  // Reinitializing CamerasChild will change the pointers below.
+  // We don't want this to happen in the middle of preparing IPC.
+  // We will be alive on destruction, so this needs to be off the books.
+  mozilla::OffTheBooksMutex mCamerasMutex;
+
+  // This is owned by the IPC code, and the same code controls the lifetime.
+  // It will set and clear this pointer as appropriate in setup/teardown.
+  // We'd normally make this a WeakPtr but unfortunately the IPC code already
+  // uses the WeakPtr mixin in a protected base class of CamerasChild, and in
+  // any case the object becomes unusable as soon as IPC is tearing down, which
+  // will be before actual destruction.
+  CamerasChild* mCameras;
+  nsCOMPtr<nsIThread> mCamerasChildThread;
+};
+
+// Get a pointer to a CamerasChild object we can use to do IPC with.
+// This does everything needed to set up, including starting the IPC
+// channel with PBackground, blocking until thats done, and starting the
+// thread to do IPC on. This will fail if we're in shutdown. On success
+// it will set up the CamerasSingleton.
+CamerasChild* GetCamerasChild();
+
+// Shut down the IPC channel and everything associated, like WebRTC.
+// This is a static call because the CamerasChild object may not even
+// be alive when we're called.
 void Shutdown(void);
 
+// Obtain the CamerasChild object (if possible, i.e. not shutting down),
+// and maintain a grip on the object for the duration of the call.
+template <class MEM_FUN, class... ARGS>
+int GetChildAndCall(MEM_FUN&& f, ARGS&&... args)
+{
+  OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
+  CamerasChild* child = GetCamerasChild();
+  if (child) {
+    return (child->*f)(mozilla::Forward<ARGS>(args)...);
+  } else {
+    return -1;
+  }
+}
+
 class CamerasChild final : public PCamerasChild
 {
   friend class mozilla::ipc::BackgroundChildImpl;
 
 public:
   // We are owned by the PBackground thread only. CamerasSingleton
   // takes a non-owning reference.
   NS_INLINE_DECL_REFCOUNTING(CamerasChild)
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -27,25 +27,35 @@ function mustFailWith(msg, reason, const
 
 var pushPrefs = (...p) => new Promise(r => SpecialPowers.pushPrefEnv({set: p}, r));
 
 runTest(() =>
   pushPrefs(["media.navigator.streams.fake", true])
   .then(() => navigator.mediaDevices.enumerateDevices())
   .then(devices => {
     ok(devices.length > 0, "At least one device found");
-    devices.forEach(d => {
+    var jsoned = JSON.parse(JSON.stringify(devices));
+    is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
+    is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
+    return devices.reduce((p, d) => {
       ok(d.kind == "videoinput" || d.kind == "audioinput", "Known device kind");
       is(d.deviceId.length, 44, "Correct device id length");
       ok(d.label.length !== undefined, "Device label: " + d.label);
       is(d.groupId, "", "Don't support groupId yet");
-    });
-    var jsoned = JSON.parse(JSON.stringify(devices));
-    is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
-    is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
+      var c = {};
+      c[d.kind == "videoinput" ? "video" : "audio"] = { deviceId: d.deviceId };
+      return p
+        .then(() => navigator.mediaDevices.getUserMedia(c))
+        .then(stream => {
+          stream.getTracks().forEach(track => {
+            is(typeof(track.label), "string", "Label is a string")
+            is(track.label, d.label, "Label is the device label")
+          })
+        });
+    }, Promise.resolve());
   })
   // Check deviceId failure paths for video.
   .then(() => mustSucceed("unknown plain deviceId on video",
                           () => navigator.mediaDevices.getUserMedia({
     video: { deviceId: "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=" },
   })))
   .then(() => mustSucceed("unknown plain deviceId on audio",
                           () => navigator.mediaDevices.getUserMedia({
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -29,17 +29,17 @@ MediaStreamAudioDestinationNode::MediaSt
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mDOMStream(
       DOMAudioNodeMediaStream::CreateTrackUnionStream(GetOwner(),
                                                       this,
                                                       aContext->Graph()))
 {
   // Ensure an audio track with the correct ID is exposed to JS
-  mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
+  mDOMStream->CreateOwnDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO, nsString());
 
   ProcessedMediaStream* outputStream = mDOMStream->GetInputStream()->AsProcessedStream();
   MOZ_ASSERT(!!outputStream);
   AudioNodeEngine* engine = new AudioNodeEngine(this);
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::EXTERNAL_OUTPUT);
   mPort = outputStream->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK);
 
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -12,16 +12,22 @@
 #include "CamerasChild.h"
 
 extern mozilla::LogModule* GetMediaManagerLog();
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 #define LOGFRAME(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
 
 namespace mozilla {
 
+// These need a definition somewhere because template
+// code is allowed to take their address, and they aren't
+// guaranteed to have one without this.
+const unsigned int MediaEngineSource::kMaxDeviceNameLength;
+const unsigned int MediaEngineSource::kMaxUniqueIdLength;;
+
 using dom::ConstrainLongRange;
 
 NS_IMPL_ISUPPORTS0(MediaEngineRemoteVideoSource)
 
 MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
   int aIndex, mozilla::camera::CaptureEngine aCapEngine,
   dom::MediaSourceEnum aMediaSource, const char* aMonitorName)
   : MediaEngineCameraVideoSource(aIndex, aMonitorName),
@@ -33,20 +39,21 @@ MediaEngineRemoteVideoSource::MediaEngin
 }
 
 void
 MediaEngineRemoteVideoSource::Init()
 {
   LOG((__PRETTY_FUNCTION__));
   char deviceName[kMaxDeviceNameLength];
   char uniqueId[kMaxUniqueIdLength];
-  if (mozilla::camera::GetCaptureDevice(mCapEngine,
-                                        mCaptureIndex,
-                                        deviceName, kMaxDeviceNameLength,
-                                        uniqueId, kMaxUniqueIdLength)) {
+  if (mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::GetCaptureDevice,
+    mCapEngine, mCaptureIndex,
+    deviceName, kMaxDeviceNameLength,
+    uniqueId, kMaxUniqueIdLength)) {
     LOG(("Error initializing RemoteVideoSource (GetCaptureDevice)"));
     return;
   }
 
   SetName(NS_ConvertUTF8toUTF16(deviceName));
   SetUUID(uniqueId);
 
   mInitDone = true;
@@ -106,19 +113,19 @@ MediaEngineRemoteVideoSource::Allocate(c
   if (mState == kReleased) {
     // Note: if shared, we don't allow a later opener to affect the resolution.
     // (This may change depending on spec changes for Constraints/settings)
 
     if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
       return NS_ERROR_UNEXPECTED;
     }
 
-    if (mozilla::camera::AllocateCaptureDevice(mCapEngine,
-                                               GetUUID().get(),
-                                               kMaxUniqueIdLength, mCaptureIndex)) {
+    if (mozilla::camera::GetChildAndCall(
+      &mozilla::camera::CamerasChild::AllocateCaptureDevice,
+      mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex)) {
       return NS_ERROR_FAILURE;
     }
     mState = kAllocated;
     LOG(("Video device %d allocated", mCaptureIndex));
   } else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
     MonitorAutoLock lock(mMonitor);
     if (mSources.IsEmpty()) {
       LOG(("Video device %d reallocated", mCaptureIndex));
@@ -140,17 +147,19 @@ MediaEngineRemoteVideoSource::Deallocate
 
   --mNrAllocations;
   MOZ_ASSERT(mNrAllocations >= 0, "Double-deallocations are prohibited");
 
   if (mNrAllocations == 0) {
     if (mState != kStopped && mState != kAllocated) {
       return NS_ERROR_FAILURE;
     }
-    mozilla::camera::ReleaseCaptureDevice(mCapEngine, mCaptureIndex);
+    mozilla::camera::GetChildAndCall(
+      &mozilla::camera::CamerasChild::ReleaseCaptureDevice,
+      mCapEngine, mCaptureIndex);
     mState = kReleased;
     LOG(("Video device %d deallocated", mCaptureIndex));
   } else {
     LOG(("Video device %d deallocated but still in use", mCaptureIndex));
   }
   return NS_OK;
 }
 
@@ -174,18 +183,19 @@ MediaEngineRemoteVideoSource::Start(Sour
   if (mState == kStarted) {
     return NS_OK;
   }
   mImageContainer = layers::LayerManager::CreateImageContainer();
 
   mState = kStarted;
   mTrackID = aID;
 
-  if (mozilla::camera::StartCapture(mCapEngine,
-                                    mCaptureIndex, mCapability, this)) {
+  if (mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::StartCapture,
+    mCapEngine, mCaptureIndex, mCapability, this)) {
     LOG(("StartCapture failed"));
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
@@ -212,17 +222,19 @@ MediaEngineRemoteVideoSource::Stop(mozil
     }
 
     mState = kStopped;
     // Drop any cached image so we don't start with a stale image on next
     // usage
     mImage = nullptr;
   }
 
-  mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
+  mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::StopCapture,
+    mCapEngine, mCaptureIndex);
 
   return NS_OK;
 }
 
 nsresult
 MediaEngineRemoteVideoSource::Restart(const dom::MediaTrackConstraints& aConstraints,
                                       const MediaEnginePrefs& aPrefs,
                                       const nsString& aDeviceId)
@@ -234,19 +246,22 @@ MediaEngineRemoteVideoSource::Restart(co
   }
   if (!ChooseCapability(aConstraints, aPrefs, aDeviceId)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   if (mState != kStarted) {
     return NS_OK;
   }
 
-  mozilla::camera::StopCapture(mCapEngine, mCaptureIndex);
-  if (mozilla::camera::StartCapture(mCapEngine,
-                                    mCaptureIndex, mCapability, this)) {
+  mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::StopCapture,
+    mCapEngine, mCaptureIndex);
+  if (mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::StartCapture,
+    mCapEngine, mCaptureIndex, mCapability, this)) {
     LOG(("StartCapture failed"));
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 void
 MediaEngineRemoteVideoSource::NotifyPull(MediaStreamGraph* aGraph,
@@ -349,17 +364,20 @@ MediaEngineRemoteVideoSource::DeliverFra
   }
 
   return 0;
 }
 
 size_t
 MediaEngineRemoteVideoSource::NumCapabilities()
 {
-  int num = mozilla::camera::NumberOfCapabilities(mCapEngine, GetUUID().get());
+  int num = mozilla::camera::GetChildAndCall(
+      &mozilla::camera::CamerasChild::NumberOfCapabilities,
+      mCapEngine,
+      GetUUID().get());
   if (num > 0) {
     return num;
   }
 
   switch(mMediaSource) {
   case dom::MediaSourceEnum::Camera:
 #ifdef XP_MACOSX
     // Mac doesn't support capabilities.
@@ -428,33 +446,36 @@ MediaEngineRemoteVideoSource::ChooseCapa
 
 void
 MediaEngineRemoteVideoSource::GetCapability(size_t aIndex,
                                             webrtc::CaptureCapability& aOut)
 {
   if (!mHardcodedCapabilities.IsEmpty()) {
     MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
   }
-  mozilla::camera::GetCaptureCapability(mCapEngine,
-                                        GetUUID().get(),
-                                        aIndex,
-                                        aOut);
+  mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::GetCaptureCapability,
+    mCapEngine,
+    GetUUID().get(),
+    aIndex,
+    aOut);
 }
 
 void MediaEngineRemoteVideoSource::Refresh(int aIndex) {
   // NOTE: mCaptureIndex might have changed when allocated!
   // Use aIndex to update information, but don't change mCaptureIndex!!
   // Caller looked up this source by uniqueId, so it shouldn't change
   char deviceName[kMaxDeviceNameLength];
   char uniqueId[kMaxUniqueIdLength];
 
-  if (mozilla::camera::GetCaptureDevice(mCapEngine,
-                                        aIndex,
-                                        deviceName, sizeof(deviceName),
-                                        uniqueId, sizeof(uniqueId))) {
+  if (mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::GetCaptureDevice,
+    mCapEngine, aIndex,
+    deviceName, sizeof(deviceName),
+    uniqueId, sizeof(uniqueId))) {
     return;
   }
 
   SetName(NS_ConvertUTF8toUTF16(deviceName));
 #ifdef DEBUG
   MOZ_ASSERT(GetUUID().Equals(uniqueId));
 #endif
 }
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -156,50 +156,57 @@ MediaEngineWebRTC::EnumerateVideoDevices
    * We still enumerate every time, in case a new device was plugged in since
    * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
    * new devices (with or without new engine creation) and accordingly adjust.
    * Enumeration is not neccessary if GIPS reports the same set of devices
    * for a given instance of the engine. Likewise, if a device was plugged out,
    * mVideoSources must be updated.
    */
   int num;
-  num = mozilla::camera::NumberOfCaptureDevices(capEngine);
+  num = mozilla::camera::GetChildAndCall(
+    &mozilla::camera::CamerasChild::NumberOfCaptureDevices,
+    capEngine);
   if (num <= 0) {
     return;
   }
 
   for (int i = 0; i < num; i++) {
     char deviceName[MediaEngineSource::kMaxDeviceNameLength];
     char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
 
     // paranoia
     deviceName[0] = '\0';
     uniqueId[0] = '\0';
     int error;
 
-    error = mozilla::camera::GetCaptureDevice(capEngine,
-                                              i, deviceName,
-                                              sizeof(deviceName), uniqueId,
-                                              sizeof(uniqueId));
-
+    error =  mozilla::camera::GetChildAndCall(
+      &mozilla::camera::CamerasChild::GetCaptureDevice,
+      capEngine,
+      i, deviceName,
+      sizeof(deviceName), uniqueId,
+      sizeof(uniqueId));
     if (error) {
       LOG(("camera:GetCaptureDevice: Failed %d", error ));
       continue;
     }
 #ifdef DEBUG
     LOG(("  Capture Device Index %d, Name %s", i, deviceName));
 
     webrtc::CaptureCapability cap;
-    int numCaps = mozilla::camera::NumberOfCapabilities(capEngine,
-                                                        uniqueId);
+    int numCaps = mozilla::camera::GetChildAndCall(
+      &mozilla::camera::CamerasChild::NumberOfCapabilities,
+      capEngine,
+      uniqueId);
     LOG(("Number of Capabilities %d", numCaps));
     for (int j = 0; j < numCaps; j++) {
-      if (mozilla::camera::GetCaptureCapability(capEngine,
-                                                uniqueId,
-                                                j, cap ) != 0 ) {
+      if (mozilla::camera::GetChildAndCall(
+            &mozilla::camera::CamerasChild::GetCaptureCapability,
+            capEngine,
+            uniqueId,
+            j, cap) != 0) {
        break;
       }
       LOG(("type=%d width=%d height=%d maxFPS=%d",
            cap.rawType, cap.width, cap.height, cap.maxFPS ));
     }
 #endif
 
     if (uniqueId[0] == '\0') {
--- a/dom/settings/SettingsDB.jsm
+++ b/dom/settings/SettingsDB.jsm
@@ -35,17 +35,17 @@ const TYPED_ARRAY_THINGS = new Set([
   "Uint16Array",
   "Int32Array",
   "Uint32Array",
   "Float32Array",
   "Float64Array",
 ]);
 
 this.SETTINGSDB_NAME = "settings";
-this.SETTINGSDB_VERSION = 7;
+this.SETTINGSDB_VERSION = 8;
 this.SETTINGSSTORE_NAME = "settings";
 
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 this.SettingsDB = function SettingsDB() {}
 
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/frameLocalStorageSessionOnly.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<html>
+  <body>
+    <script>
+      parent.postMessage(SpecialPowers.wrap(localStorage).isSessionOnly, "*");
+    </script>
+  </body>
+</html>
--- a/dom/tests/mochitest/localstorage/mochitest.ini
+++ b/dom/tests/mochitest/localstorage/mochitest.ini
@@ -11,16 +11,17 @@ support-files =
   frameQuotaSessionOnly.html
   frameReplace.html
   frameSlaveEqual.html
   frameSlaveNotEqual.html
   interOriginFrame.js
   interOriginTest.js
   interOriginTest2.js
   localStorageCommon.js
+  frameLocalStorageSessionOnly.html
 
 [test_appIsolation.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #bug 793211 # b2g(needs https to work) b2g-debug(needs https to work) b2g-desktop(needs https to work)
 [test_brokenUTF-16.html]
 [test_bug600307-DBOps.html]
 [test_bug746272-1.html]
 [test_bug746272-2.html]
 skip-if = os == "android" || toolkit == 'gonk' # bug 962029
@@ -51,8 +52,9 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_localStorageQuotaSessionOnly.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(needs https support)
 [test_localStorageQuotaSessionOnly2.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(needs https support)
 [test_localStorageReplace.html]
 skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || toolkit == 'android'
 [test_lowDeviceStorage.html]
 [test_storageConstructor.html]
+[test_localStorageSessionPrefOverride.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/localstorage/test_localStorageSessionPrefOverride.html
@@ -0,0 +1,54 @@
+<html>
+  <head>
+    <title>Local Storage Session Pref Override</title>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script>
+      const ACCEPT_SESSION = 2;
+
+      add_task(function*() {
+        yield new Promise((resolve, reject) => {
+          SpecialPowers.pushPrefEnv({"set": [["network.cookie.lifetimePolicy",
+                                              ACCEPT_SESSION]]},
+                                    resolve);
+        });
+
+        // Before setting permission
+        yield new Promise((resolve) => {
+          var frame = document.createElement('iframe');
+          frame.src = "frameLocalStorageSessionOnly.html";
+
+          var listener = (e) => {
+            is(e.data, true, "Before adding permission should be session only");
+            window.removeEventListener('message', listener);
+            resolve();
+          };
+          window.addEventListener('message', listener);
+          document.body.appendChild(frame);
+        });
+
+        // After setting permission
+        yield new Promise((resolve) => {
+          SpecialPowers.pushPermissions([{"type": "cookie", "allow": 1, "context": document}],
+                                        resolve);
+        });
+
+        yield new Promise((resolve) => {
+          var frame = document.createElement('iframe');
+          frame.src = "frameLocalStorageSessionOnly.html";
+
+          var listener = (e) => {
+            is(e.data, false, "After adding permission should not be session only");
+            window.removeEventListener('message', listener);
+            resolve();
+          };
+          window.addEventListener('message', listener);
+          document.body.appendChild(frame);
+        });
+      });
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
--- a/dom/tests/mochitest/pointerlock/file_infiniteMovement.html
+++ b/dom/tests/mochitest/pointerlock/file_infiniteMovement.html
@@ -44,55 +44,63 @@ https://bugzilla.mozilla.org/show_bug.cg
                "Should have moved more than one screen's worth in width." +
                "TotalX: " + totalMovementX + " Screensize X: " + div.getBoundingClientRect().width);
           ok(totalMovementY > div.getBoundingClientRect().height,
              "Should have moved more than one screen's worth in height." +
              "TotalY: " + totalMovementY + " Screensize Y: " + div.getBoundingClientRect().height);
         }
 
         var firstMoveListener = function (e) {
+          info("Got first mousemove");
           div.removeEventListener("mousemove", firstMoveListener, false);
           div.addEventListener("mousemove", secondMoveListener, false);
 
           synthesizeMouse(div,(divCenterWidth/2) * 3,
             (divCenterHeight/2) * 3, {
             type: "mousemove"
           }, window);
         }
 
         var secondMoveListener = function (e) {
+          info("Got second mousemove");
           totalMovementX = divCenterWidth + ((divCenterWidth / 2) * 3);
           totalMovementY = divCenterHeight + ((divCenterHeight / 2) * 3);
 
           div.removeEventListener("mousemove", secondMoveListener, false);
           document.mozCancelFullScreen();
         }
 
         document.addEventListener("mozpointerlockchange", function (e) {
           if (document.mozPointerLockElement === div) {
+            info("Got mozpointerlockchange for entering");
             div.addEventListener("mousemove", firstMoveListener, false);
 
             divCenterWidth = Math.round(div.getBoundingClientRect().width / 2);
             divCenterHeight = Math.round(div.getBoundingClientRect().height / 2);
 
             synthesizeMouse(div, divCenterWidth, divCenterHeight, {
               type: "mousemove"
             }, window);
+          } else {
+            info("Got mozpointerlockchange for exiting");
           }
         }, false);
 
         document.addEventListener("mozfullscreenchange", function() {
           if (document.mozFullScreenElement === div) {
+            info("Got mozfullscreenchange for entering");
             div.mozRequestPointerLock();
           }
           else {
+            info("Got mozfullscreenchange for exiting");
             runTests();
             SimpleTest.finish();
           }
         }, false);
 
         function start() {
+          info("Requesting fullscreen on parent");
           div.mozRequestFullScreen();
         }
       </script>
     </pre>
   </body>
 </html>
--- a/dom/tests/mochitest/pointerlock/file_movementXY.html
+++ b/dom/tests/mochitest/pointerlock/file_movementXY.html
@@ -52,16 +52,17 @@ https://bugzilla.mozilla.org/show_bug.cg
             "mozMovementY should exist in mouse events objects.");
           is(secondMove.mozMovementX, secondMove.screenX - firstMove.screenX,
            "mozMovementX should be equal to eNow.screenX-ePrevious.screenX");
           is(secondMove.mozMovementY, secondMove.screenY - firstMove.screenY,
            "mozMovementY should be equal to eNow.screenY-ePrevious.screenY");
         }
 
         var moveMouse = function(e) {
+          info("Got mouse move");
           mozMovementX = ("mozMovementX" in e);
           mozMovementY = ("mozMovementY" in e);
 
           div.removeEventListener("mousemove", moveMouse, false);
           div.addEventListener("mousemove", moveMouseAgain, false);
 
           firstMove.screenX = e.screenX;
           firstMove.screenY = e.screenY;
@@ -70,45 +71,50 @@ https://bugzilla.mozilla.org/show_bug.cg
           divCenterHeight = Math.round(div.getBoundingClientRect().height / 2);
 
           synthesizeMouse(div, (divCenterWidth + 10), (divCenterHeight + 10), {
             type: "mousemove"
           }, window);
         };
 
         var moveMouseAgain = function(e) {
+          info("Got mouse move again");
           secondMove.screenX = e.screenX;
           secondMove.screenY = e.screenY;
           secondMove.mozMovementX = e.mozMovementX;
           secondMove.mozMovementY = e.mozMovementY;
 
           div.removeEventListener("mousemove", moveMouseAgain, false);
           document.mozCancelFullScreen();
         };
 
         function fullscreenchange() {
           if (document.mozFullScreenElement === div) {
+            info("Got mozfullscreenchange for entering");
             var screenX = window.screenX;
             var screenY = window.screenY;
             if (screenX != 0 || screenY != 0) {
               todo(screenX == 0 && screenY == 0,
                 "We should only receive fullscreenchange once we've finished fullscreen transition");
               setTimeout(fullscreenchange, 250);
               return;
             } 
+            info("Finish waiting for fullscreenchange");
             div.addEventListener("mousemove", moveMouse, false);
             synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
           }
           else {
+            info("Got mozfullscreenchange for exiting");
             runTests();
             SimpleTest.finish();
           }
         }
         
         document.addEventListener("mozfullscreenchange", fullscreenchange, false);
 
         function start() {
+          info("Requesting fullscreen on parent");
           div.mozRequestFullScreen();
         }
       </script>
     </pre>
   </body>
 </html>
--- a/dom/tests/mochitest/pointerlock/file_pointerlock-api.html
+++ b/dom/tests/mochitest/pointerlock/file_pointerlock-api.html
@@ -44,57 +44,63 @@
         ok(hasExitPointerLock, "Document should have mozExitPointerLock");
         ok(pointerLockElement, "Document should keep track of correct pointer locked element");
         ok(hasMovementX, "Mouse Event should have mozMovementX.");
         ok(hasMovementY, "Mouse Event should have mozMovementY.");
         ok(!gotContextMenuEvent, "Shouldn't have got a contextmenu event.");
       }
 
       function mouseMoveHandler(e) {
+        info("Got mousemove");
         document.removeEventListener("mousemove", mouseMoveHandler, false);
 
         hasMovementX = "mozMovementX" in e;
         hasMovementY = "mozMovementY" in e;
 
         hasExitPointerLock = "mozExitPointerLock" in document;
         document.mozExitPointerLock();
       }
 
       document.addEventListener("mozpointerlockchange", function (e) {
         pointerLockChangeEventFired = true;
 
         if (document.mozPointerLockElement) {
+          info("Got mozpointerlockchange for entering");
           pointerLocked = true;
           pointerLockElement = document.mozPointerLockElement === div;
  
           window.addEventListener("contextmenu",
                                   function() { gotContextMenuEvent = true; },
                                   true);
           synthesizeMouse(document.body, 4, 4,
                           { type: "contextmenu", button: 2 },
                           window);
 
           document.addEventListener("mousemove", mouseMoveHandler, false);
           synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
         } else {
+          info("Got mozpointerlockchange for exiting");
           pointerUnlocked = true;
           document.mozCancelFullScreen();
         }
       }, false);
 
       document.addEventListener("mozfullscreenchange", function(e) {
         if (document.mozFullScreenElement === div) {
+          info("Got mozfullscreenchange for entering");
           hasRequestPointerLock = "mozRequestPointerLock" in div;
           div.mozRequestPointerLock();
         } else {
+          info("Got mozfullscreenchange for exiting");
           runTests();
           SimpleTest.finish();
         }
       }, false);
 
       function start() {
         div = document.getElementById("div");
+        info("Requesting fullscreen on parent");
         div.mozRequestFullScreen();
       }
     </script>
   </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html
+++ b/dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html
@@ -88,108 +88,122 @@ https://bugzilla.mozilla.org/show_bug.cg
       };
 
       var childWheelTest = function() {
         childStats.wheel = true;
       };
 
       //  Event listeners for the parent element
       var startMouseTests = function() {
+        info("Got parent mousemove");
         parent.removeEventListener("mousemove", startMouseTests);
         parent.addEventListener("DOMMouseScroll", parentScrollTest);
         child.addEventListener("DOMMouseScroll", childScrollTest);
         SimpleTest.executeSoon(function () {
           synthesizeWheel(child, 5, 5, {'deltaY': 10, 'lineOrPageDeltaY': 10,
                                         'deltaMode': WheelEvent.DOM_DELTA_LINE});
         });
       };
 
       var parentScrollTest = function (e) {
+        info("Got parent DOMMouseScroll");
         parentStats.mouseScroll = true;
         parent.removeEventListener("DOMMouseScroll", parentScrollTest);
         child.removeEventListener("DOMMouseScroll", childScrollTest);
         parent.addEventListener("wheel", parentWheelTest);
         child.addEventListener("wheel", childWheelTest);
         SimpleTest.executeSoon(function () {
           synthesizeWheel(child, 5, 5, {'deltaY': 10, 'lineOrPageDeltaY': 10,
                                         'deltaMode': WheelEvent.DOM_DELTA_LINE});
         });
       };
 
       var parentWheelTest = function (e) {
+        info("Got parent wheel");
         parentStats.wheel = true;
         parent.removeEventListener("wheel", parentWheelTest);
         child.removeEventListener("wheel", childWheelTest);
         parent.addEventListener("mousedown", parentDownTest);
         child.addEventListener("mousedown", childDownTest);
         SimpleTest.executeSoon(function () {
           synthesizeMouseAtCenter(child, {type: "mousedown"}, window);
         });
       };
 
       var parentDownTest = function (e) {
+        info("Got parent mousedown");
         parentStats.mouseDown = true;
         parent.removeEventListener("mousedown", parentDownTest);
         child.removeEventListener("mousedown", childDownTest);
         parent.addEventListener("mouseup", parentUpTest);
         child.addEventListener("mouseup", childUpTest);
         SimpleTest.executeSoon(function () {
           synthesizeMouseAtCenter(child, {type: "mouseup"}, window);
         });
       };
 
       var parentUpTest = function (e) {
+        info("Got parent mouseup");
         parentStats.mouseUp = true;
         parent.removeEventListener("mouseup", parentUpTest);
         child.removeEventListener("mouseup", childUpTest);
         parent.addEventListener("click", parentClickTest);
         child.addEventListener("click", childClickTest);
         SimpleTest.executeSoon(function () {
           synthesizeMouseAtCenter(child, {}, window);
         });
       };
 
       var parentClickTest = function (e) {
+        info("Got parent click");
         parentStats.mouseClick = true;
         parent.removeEventListener("click", parentClickTest);
         child.removeEventListener("click", childClickTest);
         parent.addEventListener("mousemove", parentMoveTest);
         child.addEventListener("mousemove", childMoveTest);
         SimpleTest.executeSoon(function () {
           synthesizeMouseAtCenter(child, {type: "mousemove"}, window);
         });
       };
 
       var parentMoveTest = function (e) {
+        info("Got parent mousemove");
         parentStats.mouseMove = true;
         parent.removeEventListener("mousemove", parentMoveTest);
         child.removeEventListener("mousemove", childMoveTest);
         SimpleTest.executeSoon(function () {
+          info("Exit fullscreen");
           document.mozCancelFullScreen();
         });
       }
 
       document.addEventListener("mozpointerlockchange", function (e) {
         if (document.mozPointerLockElement === parent) {
+          info("Got mozpointerlockchange for entering");
           parent.addEventListener("mousemove", startMouseTests);
           child.addEventListener("mousemove", childMoveTest);
           SimpleTest.executeSoon(function () {
             synthesizeMouseAtCenter(parent, {type: "mousemove"}, window);
           });
+        } else {
+          info("Got mozpointerlockchange for exiting");
         }
       }, false);
 
       document.addEventListener("mozfullscreenchange", function (e)  {
         if (document.mozFullScreenElement === parent) {
+          info("Got mozfullscreenchange for entering");
           parent.mozRequestPointerLock();
         } else {
+          info("Got mozfullscreenchange for exiting");
           runTests();
           SimpleTest.finish();
         }
       }, false);
 
       function start() {
+        info("Requesting fullscreen on parent");
         parent.mozRequestFullScreen();
       }
     </script>
   </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/pointerlock/file_screenClientXYConst.html
+++ b/dom/tests/mochitest/pointerlock/file_screenClientXYConst.html
@@ -44,16 +44,17 @@ https://bugzilla.mozilla.org/show_bug.cg
            "clientY should be equal to where the mouse was originaly locked");
         is(unLockedCoords.screenX, lockedCoords.screenX,
            "screenX should be equal to where the mouse was originaly locked");
         is(unLockedCoords.screenY, lockedCoords.screenY,
            "screenY should be equal to where the mouse was originaly locked");
       }
 
       function moveUnlocked(e) {
+        info("Got mousemove via moveUnlocked");
         var firstCall = !unLockedCoords;
         if (!firstCall) {
           todo(false, "mousemove is fired twice.");
         }
 
         unLockedCoords = {
           screenX: e.screenX,
           screenY: e.screenY,
@@ -65,61 +66,69 @@ https://bugzilla.mozilla.org/show_bug.cg
           return;
         }
 
         isUnlocked = !document.mozPointerLockElement;
         div.mozRequestPointerLock();
       }
 
       function moveLocked(e) {
+        info("Got mousemove via moveLocked");
         div.removeEventListener("mousemove", moveLocked, false);
 
         isLocked = !!document.mozPointerLockElement;
         lockedCoords = {
           screenX: e.screenX,
           screenY: e.screenY,
           clientX: e.clientX,
           clientY: e.clientY
         };
 
         document.mozCancelFullScreen();
       }
 
       document.addEventListener("mozpointerlockchange", function (e) {
         if (document.mozPointerLockElement === div) {
+          info("Got mozpointerlockchange for entering");
           div.removeEventListener("mousemove", moveUnlocked, false);
           div.addEventListener("mousemove", moveLocked, false);
           divRect = div.getBoundingClientRect();
           synthesizeNativeMouseMove(div, (divRect.width / 4) * 3,
                                     (divRect.height / 4) * 3);
+        } else {
+          info("Got mozpointerlockchange for exiting");
         }
       }, false);
 
       function fullscreenchange() {
         var screenX = window.screenX;
         var screenY = window.screenY;
         if (document.mozFullScreenElement === div) {
+          info("Got mozfullscreenchange for entering");
           if (screenX != 0 || screenY != 0) {
             todo(screenX == 0 && screenY == 0,
               "We should only receive fullscreenchange once we've finished fullscreen transition " +
               "window.screenX=" + screenX + " window.screenY=" + screenY);
             setTimeout(fullscreenchange, 250);
             return;
           }
+          info("Finish waiting for fullscreenchange");
           synthesizeNativeMouseMove(div, 0, 0, () => {
             div.addEventListener("mousemove", moveUnlocked, false);
             divRect = div.getBoundingClientRect();
             synthesizeNativeMouseMove(div, divRect.width / 2, divRect.height / 2);
           });
         } else {
+          info("Got mozfullscreenchange for exiting");
           runTests();
           SimpleTest.finish();
         }
       }
       document.addEventListener("mozfullscreenchange", fullscreenchange, false);
 
       function start() {
         div = document.getElementById("div");
+        info("Requesting fullscreen on parent");
         div.mozRequestFullScreen();
       }
   </script>
 </body>
 </html>
--- a/dom/tests/mochitest/pointerlock/pointerlock_utils.js
+++ b/dom/tests/mochitest/pointerlock/pointerlock_utils.js
@@ -27,16 +27,20 @@ if (window.opener) {
   window.todo_is = function(a, b, msg) {
     opener.todo_is(a, b, testName + ": " + msg);
   };
 
   window.todo_isnot = function(a, b, msg) {
     opener.todo_isnot(a, b, testName + ": " + msg);
   };
 
+  window.info = function(msg) {
+    opener.info(testName + ": " + msg);
+  };
+
   // Override bits of SimpleTest so test files work stand-alone
   var SimpleTest = SimpleTest || {};
 
   SimpleTest.waitForExplicitFinish = function() {
     dump("[POINTERLOCK] Starting " + testName+ "\n");
   };
 
   SimpleTest.finish = function () {
--- a/dom/webidl/Storage.webidl
+++ b/dom/webidl/Storage.webidl
@@ -24,9 +24,12 @@ interface Storage {
   [Throws]
   setter creator void setItem(DOMString key, DOMString value);
 
   [Throws]
   deleter void removeItem(DOMString key);
 
   [Throws]
   void clear();
+
+  [ChromeOnly]
+  readonly attribute boolean isSessionOnly;
 };
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -378,17 +378,19 @@ public:
     if (MOZ_UNLIKELY(LogIt())) {
       mMessage << "Matrix(" << aMatrix._11 << " " << aMatrix._12 << " ; " << aMatrix._21 << " " << aMatrix._22 << " ; " << aMatrix._31 << " " << aMatrix._32 << ")";
     }
     return *this;
   }
   template<typename T>
   Log &operator<<(Hexa<T> aHex) {
     if (MOZ_UNLIKELY(LogIt())) {
-      mMessage << "0x" << std::hex << aHex.mVal << std::dec;
+      mMessage << std::showbase << std::hex
+               << aHex.mVal
+               << std::noshowbase << std::dec;
     }
     return *this;
   }
 
   Log& operator<<(SurfaceFormat aFormat) {
     if (MOZ_UNLIKELY(LogIt())) {
       switch(aFormat) {
         case SurfaceFormat::B8G8R8A8:
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -352,16 +352,38 @@ DecomposeIntoNoRepeatRects(const gfx::Re
            flipped);
   SetRects(3, aLayerRects, aTextureRects,
            xmid, ymid, aRect.XMost(), aRect.YMost(),
            0.0f, 0.0f, br.x, br.y,
            flipped);
   return 4;
 }
 
+gfx::IntRect
+Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
+                                    const gfx::Rect& aClipRect,
+                                    const gfx::Matrix4x4& aTransform)
+{
+  gfx::Rect renderBounds = mRenderBounds;
+
+  // Compute the clip.
+  renderBounds.IntersectRect(renderBounds, aClipRect);
+
+  // Apply the layer transform.
+  gfx::Rect dest = aTransform.TransformAndClipBounds(aRect, renderBounds);
+  dest.RoundOut();
+
+  gfx::IntRect result;
+  dest.ToIntRect(&result);
+
+  gfx::IntPoint offset = GetCurrentRenderTarget()->GetOrigin();
+  result.MoveBy(-offset);
+  return result;
+}
+
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 void
 Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
 {
   // OpenGL does not provide ReleaseFence for rendering.
   // Instead use DispAcquireFence as layer buffer's ReleaseFence
   // to prevent flickering and tearing.
   // DispAcquireFence is DisplaySurface's AcquireFence.
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -114,16 +114,17 @@ class Matrix;
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 
 struct Effect;
 struct EffectChain;
 class Image;
+class ImageHostOverlay;
 class Layer;
 class TextureSource;
 class DataTextureSource;
 class CompositingRenderTarget;
 class PCompositorParent;
 class LayerManagerComposite;
 
 enum SurfaceInitMode
@@ -448,16 +449,22 @@ public:
    * composite. Returns false if rendering should be aborted.
    */
   virtual bool Ready() { return true; }
 
   // XXX I expect we will want to move mWidget into this class and implement
   // these methods properly.
   virtual nsIWidget* GetWidget() const { return nullptr; }
 
+  virtual bool HasImageHostOverlays() { return false; }
+
+  virtual void AddImageHostOverlay(ImageHostOverlay* aOverlay) {}
+
+  virtual void RemoveImageHostOverlay(ImageHostOverlay* aOverlay) {}
+
   /**
    * Debug-build assertion that can be called to ensure code is running on the
    * compositor thread.
    */
   static void AssertOnCompositorThread();
 
   size_t GetFillRatio() {
     float fillRatio = 0;
@@ -503,16 +510,26 @@ protected:
                                const gfx::Rect& aVisibleRect,
                                const gfx::Rect& aClipRect,
                                const gfx::Matrix4x4& transform,
                                uint32_t aFlashCounter);
 
   bool ShouldDrawDiagnostics(DiagnosticFlags);
 
   /**
+   * Given a layer rect, clip, and transform, compute the area of the backdrop that
+   * needs to be copied for mix-blending. The output transform translates from 0..1
+   * space into the backdrop rect space.
+   */
+  gfx::IntRect ComputeBackdropCopyRect(
+    const gfx::Rect& aRect,
+    const gfx::Rect& aClipRect,
+    const gfx::Matrix4x4& aTransform);
+
+  /**
    * 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).
    */
@@ -530,28 +547,55 @@ protected:
   size_t mPixelsPerFrame;
   size_t mPixelsFilled;
 
   ScreenRotation mScreenRotation;
 
   RefPtr<gfx::DrawTarget> mTarget;
   gfx::IntRect mTargetBounds;
 
+  gfx::Rect mRenderBounds;
+
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   FenceHandle mReleaseFenceHandle;
 #endif
 
 private:
   static LayersBackend sBackend;
 
 };
 
 // Returns the number of rects. (Up to 4)
 typedef gfx::Rect decomposedRectArrayT[4];
 size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
                                   const gfx::Rect& aTexCoordRect,
                                   decomposedRectArrayT* aLayerRects,
                                   decomposedRectArrayT* aTextureRects);
 
+static inline bool
+BlendOpIsMixBlendMode(gfx::CompositionOp aOp)
+{
+  switch (aOp) {
+  case gfx::CompositionOp::OP_MULTIPLY:
+  case gfx::CompositionOp::OP_SCREEN:
+  case gfx::CompositionOp::OP_OVERLAY:
+  case gfx::CompositionOp::OP_DARKEN:
+  case gfx::CompositionOp::OP_LIGHTEN:
+  case gfx::CompositionOp::OP_COLOR_DODGE:
+  case gfx::CompositionOp::OP_COLOR_BURN:
+  case gfx::CompositionOp::OP_HARD_LIGHT:
+  case gfx::CompositionOp::OP_SOFT_LIGHT:
+  case gfx::CompositionOp::OP_DIFFERENCE:
+  case gfx::CompositionOp::OP_EXCLUSION:
+  case gfx::CompositionOp::OP_HUE:
+  case gfx::CompositionOp::OP_SATURATION:
+  case gfx::CompositionOp::OP_COLOR:
+  case gfx::CompositionOp::OP_LUMINOSITY:
+    return true;
+  default:
+    return false;
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_COMPOSITOR_H */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2757,17 +2757,17 @@ void AsyncPanZoomController::FlushRepain
   RequestContentRepaint();
   UpdateSharedCompositorFrameMetrics();
 }
 
 void AsyncPanZoomController::FlushRepaintForNewInputBlock() {
   APZC_LOG("%p flushing repaint for new input block\n", this);
 
   ReentrantMonitorAutoEnter lock(mMonitor);
-  RequestContentRepaint(mFrameMetrics);
+  RequestContentRepaint();
   UpdateSharedCompositorFrameMetrics();
 }
 
 bool AsyncPanZoomController::SnapBackIfOverscrolled() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   // It's possible that we're already in the middle of an overscroll
   // animation - if so, don't start a new one.
   if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) {
@@ -2800,24 +2800,54 @@ bool AsyncPanZoomController::IsPannable(
 }
 
 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   return listener ? listener->GetLastTouchIdentifier() : -1;
 }
 
 void AsyncPanZoomController::RequestContentRepaint() {
-  RequestContentRepaint(mFrameMetrics);
+  // Reinvoke this method on the main thread if it's not there already. It's
+  // important to do this before the call to CalculatePendingDisplayPort, so
+  // that CalculatePendingDisplayPort uses the most recent available version of
+  // mFrameMetrics, just before the paint request is dispatched to content.
+  if (!NS_IsMainThread()) {
+    // use the local variable to resolve the function overload.
+    auto func = static_cast<void (AsyncPanZoomController::*)()>
+        (&AsyncPanZoomController::RequestContentRepaint);
+    NS_DispatchToMainThread(NS_NewRunnableMethod(this, func));
+    return;
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  ParentLayerPoint velocity = GetVelocityVector();
+  mFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(mFrameMetrics, velocity));
+  mFrameMetrics.SetUseDisplayPortMargins(true);
+  mFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
+  RequestContentRepaint(mFrameMetrics, velocity);
 }
 
-void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics)
+/*static*/ CSSRect
+GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
 {
-  ParentLayerPoint velocity = GetVelocityVector();
-  aFrameMetrics.SetDisplayPortMargins(CalculatePendingDisplayPort(aFrameMetrics, velocity));
-  aFrameMetrics.SetUseDisplayPortMargins(true);
+  // This computation is based on what happens in CalculatePendingDisplayPort. If that
+  // changes then this might need to change too
+  CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
+                   aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
+  baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
+  return baseRect;
+}
+
+void
+AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics,
+                                              const ParentLayerPoint& aVelocity)
+{
+  MOZ_ASSERT(NS_IsMainThread());
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins()
                            - aFrameMetrics.GetDisplayPortMargins());
   if (fabsf(marginDelta.left) < EPSILON &&
       fabsf(marginDelta.top) < EPSILON &&
       fabsf(marginDelta.right) < EPSILON &&
@@ -2830,57 +2860,32 @@ void AsyncPanZoomController::RequestCont
       fabsf(aFrameMetrics.GetViewport().width -
             mLastPaintRequestMetrics.GetViewport().width) < EPSILON &&
       fabsf(aFrameMetrics.GetViewport().height -
             mLastPaintRequestMetrics.GetViewport().height) < EPSILON &&
       aFrameMetrics.GetScrollGeneration() == mLastPaintRequestMetrics.GetScrollGeneration()) {
     return;
   }
 
-  aFrameMetrics.SetPaintRequestTime(TimeStamp::Now());
-  DispatchRepaintRequest(aFrameMetrics, velocity);
-  aFrameMetrics.SetPresShellId(mLastContentPaintMetrics.GetPresShellId());
-}
-
-/*static*/ CSSRect
-GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
-{
-  // This computation is based on what happens in CalculatePendingDisplayPort. If that
-  // changes then this might need to change too
-  CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
-                   aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
-  baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
-  return baseRect;
-}
-
-void
-AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics,
-                                               const ParentLayerPoint& aVelocity)
-{
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
 
   APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
   if (mCheckerboardEvent) {
     std::stringstream info;
     info << " velocity " << aVelocity;
     std::string str = info.str();
     mCheckerboardEvent->UpdateRendertraceProperty(
         CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics),
         str);
   }
 
-  if (NS_IsMainThread()) {
-    controller->RequestContentRepaint(aFrameMetrics);
-  } else {
-    NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>(
-      controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics));
-  }
+  controller->RequestContentRepaint(aFrameMetrics);
   mExpectedGeckoMetrics = aFrameMetrics;
   mLastPaintRequestMetrics = aFrameMetrics;
 }
 
 bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
                                              Vector<Task*>* aOutDeferredTasks)
 {
   APZThreadUtils::AssertOnCompositorThread();
@@ -3455,29 +3460,40 @@ void AsyncPanZoomController::ZoomToRect(
     if (!zoomOut && (sizeAfterZoom.height > aRect.height)) {
       aRect.y -= (sizeAfterZoom.height - aRect.height) * 0.5f;
       if (aRect.y < 0.0f) {
         aRect.y = 0.0f;
       }
     }
 
     endZoomToMetrics.SetScrollOffset(aRect.TopLeft());
-    endZoomToMetrics.SetDisplayPortMargins(
-      CalculatePendingDisplayPort(endZoomToMetrics, ParentLayerPoint(0,0)));
-    endZoomToMetrics.SetUseDisplayPortMargins(true);
 
     StartAnimation(new ZoomAnimation(
         mFrameMetrics.GetScrollOffset(),
         mFrameMetrics.GetZoom(),
         endZoomToMetrics.GetScrollOffset(),
         endZoomToMetrics.GetZoom()));
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
-    RequestContentRepaint(endZoomToMetrics);
+    ParentLayerPoint velocity(0, 0);
+    endZoomToMetrics.SetDisplayPortMargins(
+      CalculatePendingDisplayPort(endZoomToMetrics, velocity));
+    endZoomToMetrics.SetUseDisplayPortMargins(true);
+    endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now());
+    if (NS_IsMainThread()) {
+      RequestContentRepaint(endZoomToMetrics, velocity);
+    } else {
+      // use a local var to resolve the function overload
+      auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)>
+          (&AsyncPanZoomController::RequestContentRepaint);
+      NS_DispatchToMainThread(
+          NS_NewRunnableMethodWithArgs<FrameMetrics, ParentLayerPoint>(
+              this, func, endZoomToMetrics, velocity));
+    }
   }
 }
 
 CancelableBlockState*
 AsyncPanZoomController::CurrentInputBlock() const
 {
   return GetInputQueue()->CurrentBlock();
 }
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -572,37 +572,29 @@ protected:
   /**
    * Does any panning required due to a new touch event.
    */
   void TrackTouch(const MultiTouchInput& aEvent);
 
   /**
    * Utility function to send updated FrameMetrics to Gecko so that it can paint
    * the displayport area. Calls into GeckoContentController to do the actual
-   * work. Note that only one paint request can be active at a time. If a paint
-   * request is made while a paint is currently happening, it gets queued up. If
-   * a new paint request arrives before a paint is completed, the old request
-   * gets discarded.
+   * work. This call will use the current metrics. If this function is called
+   * from a non-main thread, it will redispatch itself to the main thread, and
+   * use the latest metrics during the redispatch.
    */
   void RequestContentRepaint();
 
   /**
-   * Tell the paint throttler to request a content repaint with the given
-   * metrics.  (Helper function used by RequestContentRepaint.) If aThrottled
-   * is set to false, the repaint request is sent directly without going through
-   * the paint throttler. In particular, the GeckoContentController::RequestContentRepaint
-   * function will be invoked before this function returns.
+   * Send the provided metrics to Gecko to trigger a repaint. This function
+   * may filter duplicate calls with the same metrics. This function must be
+   * called on the main thread.
    */
-  void RequestContentRepaint(FrameMetrics& aFrameMetrics);
-
-  /**
-   * Actually send the next pending paint request to gecko.
-   */
-  void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics,
-                              const ParentLayerPoint& aVelocity);
+  void RequestContentRepaint(const FrameMetrics& aFrameMetrics,
+                             const ParentLayerPoint& aVelocity);
 
   /**
    * Gets the current frame metrics. This is *not* the Gecko copy stored in the
    * layers code.
    */
   const FrameMetrics& GetFrameMetrics() const;
 
   /**
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/APZCBasicTester.h
@@ -0,0 +1,119 @@
+/* -*- 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_APZCBasicTester_h
+#define mozilla_layers_APZCBasicTester_h
+
+/**
+ * Defines a test fixture used for testing a single APZC.
+ */
+
+#include "APZTestCommon.h"
+
+class APZCBasicTester : public ::testing::Test {
+public:
+  explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
+    : mGestureBehavior(aGestureBehavior)
+  {
+  }
+
+protected:
+  virtual void SetUp()
+  {
+    gfxPrefs::GetSingleton();
+    APZThreadUtils::SetThreadAssertionsEnabled(false);
+    APZThreadUtils::SetControllerThread(MessageLoop::current());
+
+    mcc = new NiceMock<MockContentControllerDelayed>();
+    tm = new TestAPZCTreeManager(mcc);
+    apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
+    apzc->SetFrameMetrics(TestFrameMetrics());
+  }
+
+  /**
+   * Get the APZC's scroll range in CSS pixels.
+   */
+  CSSRect GetScrollRange() const
+  {
+    const FrameMetrics& metrics = apzc->GetFrameMetrics();
+    return CSSRect(
+        metrics.GetScrollableRect().TopLeft(),
+        metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels());
+  }
+
+  virtual void TearDown()
+  {
+    while (mcc->RunThroughDelayedTasks());
+    apzc->Destroy();
+  }
+
+  void MakeApzcWaitForMainThread()
+  {
+    apzc->SetWaitForMainThread();
+  }
+
+  void MakeApzcZoomable()
+  {
+    apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f)));
+  }
+
+  void MakeApzcUnzoomable()
+  {
+    apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f)));
+  }
+
+  void PanIntoOverscroll();
+
+  /**
+   * Sample animations once, 1 ms later than the last sample.
+   */
+  void SampleAnimationOnce()
+  {
+    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    mcc->AdvanceBy(increment);
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  }
+
+  /**
+   * Sample animations until we recover from overscroll.
+   * @param aExpectedScrollOffset the expected reported scroll offset
+   *                              throughout the animation
+   */
+  void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset)
+  {
+    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+    bool recoveredFromOverscroll = false;
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) {
+      // The reported scroll offset should be the same throughout.
+      EXPECT_EQ(aExpectedScrollOffset, pointOut);
+
+      // Trigger computation of the overscroll tranform, to make sure
+      // no assetions fire during the calculation.
+      apzc->GetOverscrollTransform();
+
+      if (!apzc->IsOverscrolled()) {
+        recoveredFromOverscroll = true;
+      }
+
+      mcc->AdvanceBy(increment);
+    }
+    EXPECT_TRUE(recoveredFromOverscroll);
+    apzc->AssertStateIsReset();
+  }
+
+  void TestOverscroll();
+
+  AsyncPanZoomController::GestureBehavior mGestureBehavior;
+  RefPtr<MockContentControllerDelayed> mcc;
+  RefPtr<TestAPZCTreeManager> tm;
+  RefPtr<TestAsyncPanZoomController> apzc;
+};
+
+#endif // mozilla_layers_APZCBasicTester_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h
@@ -0,0 +1,167 @@
+/* -*- 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_APZCTreeManagerTester_h
+#define mozilla_layers_APZCTreeManagerTester_h
+
+/**
+ * Defines a test fixture used for testing multiple APZCs interacting in
+ * an APZCTreeManager.
+ */
+
+#include "APZTestCommon.h"
+
+class APZCTreeManagerTester : public ::testing::Test {
+protected:
+  virtual void SetUp() {
+    gfxPrefs::GetSingleton();
+    APZThreadUtils::SetThreadAssertionsEnabled(false);
+    APZThreadUtils::SetControllerThread(MessageLoop::current());
+
+    mcc = new NiceMock<MockContentControllerDelayed>();
+    manager = new TestAPZCTreeManager(mcc);
+  }
+
+  virtual void TearDown() {
+    while (mcc->RunThroughDelayedTasks());
+    manager->ClearTree();
+  }
+
+  /**
+   * Sample animations once for all APZCs, 1 ms later than the last sample.
+   */
+  void SampleAnimationsOnce() {
+    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    mcc->AdvanceBy(increment);
+
+    for (const RefPtr<Layer>& layer : layers) {
+      if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) {
+        apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+      }
+    }
+  }
+
+  RefPtr<MockContentControllerDelayed> mcc;
+
+  nsTArray<RefPtr<Layer> > layers;
+  RefPtr<LayerManager> lm;
+  RefPtr<Layer> root;
+
+  RefPtr<TestAPZCTreeManager> manager;
+
+protected:
+  static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
+                                        CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
+    FrameMetrics metrics;
+    metrics.SetScrollId(aScrollId);
+    // By convention in this test file, START_SCROLL_ID is the root, so mark it as such.
+    if (aScrollId == FrameMetrics::START_SCROLL_ID) {
+      metrics.SetIsLayersIdRoot(true);
+    }
+    IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds();
+    metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y,
+                                                 layerBound.width, layerBound.height));
+    metrics.SetScrollableRect(aScrollableRect);
+    metrics.SetScrollOffset(CSSPoint(0, 0));
+    metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
+    metrics.SetAllowVerticalScrollWithWheel(true);
+    aLayer->SetFrameMetrics(metrics);
+    aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound)));
+    if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
+      // The purpose of this is to roughly mimic what layout would do in the
+      // case of a scrollable frame with the event regions and clip. This lets
+      // us exercise the hit-testing code in APZCTreeManager
+      EventRegions er = aLayer->GetEventRegions();
+      IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
+      er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size()));
+      aLayer->SetEventRegions(er);
+    }
+  }
+
+  void SetScrollHandoff(Layer* aChild, Layer* aParent) {
+    FrameMetrics metrics = aChild->GetFrameMetrics(0);
+    metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
+    aChild->SetFrameMetrics(metrics);
+  }
+
+  static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) {
+    EXPECT_EQ(1u, aLayer->GetFrameMetricsCount());
+    return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0);
+  }
+
+  void CreateSimpleScrollingLayer() {
+    const char* layerTreeSyntax = "t";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,200,200)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
+  }
+
+  void CreateSimpleDTCScrollingLayer() {
+    const char* layerTreeSyntax = "t";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,200,200)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
+
+    EventRegions regions;
+    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200));
+    regions.mDispatchToContentHitRegion = regions.mHitRegion;
+    layers[0]->SetEventRegions(regions);
+  }
+
+  void CreateSimpleMultiLayerTree() {
+    const char* layerTreeSyntax = "c(tt)";
+    // LayerID                     0 12
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,100,100)),
+      nsIntRegion(IntRect(0,0,100,50)),
+      nsIntRegion(IntRect(0,50,100,50)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+  }
+
+  void CreatePotentiallyLeakingTree() {
+    const char* layerTreeSyntax = "c(c(c(t))c(c(t)))";
+    // LayerID                     0 1 2 3  4 5 6
+    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
+    SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2);
+    SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3);
+  }
+
+  void CreateBug1194876Tree() {
+    const char* layerTreeSyntax = "c(t)";
+    // LayerID                     0 1
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,100,100)),
+      nsIntRegion(IntRect(0,0,100,100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
+
+    // Make layers[1] the root content
+    FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0);
+    childMetrics.SetIsRootContent(true);
+    layers[1]->SetFrameMetrics(childMetrics);
+
+    // Both layers are fully dispatch-to-content
+    EventRegions regions;
+    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
+    regions.mDispatchToContentHitRegion = regions.mHitRegion;
+    layers[0]->SetEventRegions(regions);
+    layers[1]->SetEventRegions(regions);
+  }
+};
+
+#endif // mozilla_layers_APZCTreeManagerTester_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -0,0 +1,297 @@
+/* -*- 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_APZTestCommon_h
+#define mozilla_layers_APZTestCommon_h
+
+/**
+ * Defines a set of mock classes and utility functions/classes for
+ * writing APZ gtests.
+ */
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
+#include "mozilla/layers/GeckoContentController.h"
+#include "mozilla/layers/CompositorParent.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/LayerMetricsWrapper.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "apz/src/AsyncPanZoomController.h"
+#include "apz/src/HitTestingTreeNode.h"
+#include "base/task.h"
+#include "Layers.h"
+#include "TestLayers.h"
+#include "UnitTransforms.h"
+#include "gfxPrefs.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using ::testing::_;
+using ::testing::NiceMock;
+using ::testing::AtLeast;
+using ::testing::AtMost;
+using ::testing::MockFunction;
+using ::testing::InSequence;
+
+class Task;
+
+template<class T>
+class ScopedGfxPref {
+public:
+  ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal)
+    : mSetPrefFunc(aSetPrefFunc)
+  {
+    mOldVal = aGetPrefFunc();
+    aSetPrefFunc(aVal);
+  }
+
+  ~ScopedGfxPref() {
+    mSetPrefFunc(mOldVal);
+  }
+
+private:
+  void (*mSetPrefFunc)(T);
+  T mOldVal;
+};
+
+#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \
+  ScopedGfxPref<prefType> pref_##prefBase( \
+    &(gfxPrefs::prefBase), \
+    &(gfxPrefs::Set##prefBase), \
+    prefValue)
+
+class MockContentController : public GeckoContentController {
+public:
+  MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
+  MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
+  MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
+  MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
+  MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
+  MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
+  MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
+  MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
+  MOCK_METHOD0(NotifyFlushComplete, void());
+};
+
+class MockContentControllerDelayed : public MockContentController {
+public:
+  MockContentControllerDelayed()
+    : mTime(TimeStamp::Now())
+  {
+  }
+
+  const TimeStamp& Time() {
+    return mTime;
+  }
+
+  void AdvanceByMillis(int aMillis) {
+    AdvanceBy(TimeDuration::FromMilliseconds(aMillis));
+  }
+
+  void AdvanceBy(const TimeDuration& aIncrement) {
+    TimeStamp target = mTime + aIncrement;
+    while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) {
+      RunNextDelayedTask();
+    }
+    mTime = target;
+  }
+
+  void PostDelayedTask(Task* aTask, int aDelayMs) {
+    TimeStamp runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs);
+    int insIndex = mTaskQueue.Length();
+    while (insIndex > 0) {
+      if (mTaskQueue[insIndex - 1].second <= runAtTime) {
+        break;
+      }
+      insIndex--;
+    }
+    mTaskQueue.InsertElementAt(insIndex, std::make_pair(aTask, runAtTime));
+  }
+
+  // Run all the tasks in the queue, returning the number of tasks
+  // run. Note that if a task queues another task while running, that
+  // new task will not be run. Therefore, there may be still be tasks
+  // in the queue after this function is called. Only when the return
+  // value is 0 is the queue guaranteed to be empty.
+  int RunThroughDelayedTasks() {
+    nsTArray<std::pair<Task*, TimeStamp>> runQueue;
+    runQueue.SwapElements(mTaskQueue);
+    int numTasks = runQueue.Length();
+    for (int i = 0; i < numTasks; i++) {
+      mTime = runQueue[i].second;
+      runQueue[i].first->Run();
+
+      // Deleting the task is important in order to release the reference to
+      // the callee object.
+      delete runQueue[i].first;
+    }
+    return numTasks;
+  }
+
+private:
+  void RunNextDelayedTask() {
+    std::pair<Task*, TimeStamp> next = mTaskQueue[0];
+    mTaskQueue.RemoveElementAt(0);
+    mTime = next.second;
+    next.first->Run();
+    // Deleting the task is important in order to release the reference to
+    // the callee object.
+    delete next.first;
+  }
+
+  // The following array is sorted by timestamp (tasks are inserted in order by
+  // timestamp).
+  nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue;
+  TimeStamp mTime;
+};
+
+class TestAPZCTreeManager : public APZCTreeManager {
+public:
+  explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {}
+
+  RefPtr<InputQueue> GetInputQueue() const {
+    return mInputQueue;
+  }
+
+protected:
+  AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
+                                          GeckoContentController* aController) override;
+
+  TimeStamp GetFrameTime() override {
+    return mcc->Time();
+  }
+
+private:
+  RefPtr<MockContentControllerDelayed> mcc;
+};
+
+class TestAsyncPanZoomController : public AsyncPanZoomController {
+public:
+  TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc,
+                             TestAPZCTreeManager* aTreeManager,
+                             GestureBehavior aBehavior = DEFAULT_GESTURES)
+    : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(),
+        aMcc, aBehavior)
+    , mWaitForMainThread(false)
+    , mcc(aMcc)
+  {}
+
+  nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) {
+    // This is a function whose signature matches exactly the ReceiveInputEvent
+    // on APZCTreeManager. This allows us to templates for functions like
+    // TouchDown, TouchUp, etc so that we can reuse the code for dispatching
+    // events into both APZC and APZCTM.
+    return ReceiveInputEvent(aEvent, aOutInputBlockId);
+  }
+
+  nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) {
+    return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId);
+  }
+
+  void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) {
+    GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
+  }
+
+  void ConfirmTarget(uint64_t aInputBlockId) {
+    RefPtr<AsyncPanZoomController> target = this;
+    GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target);
+  }
+
+  void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
+    GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
+  }
+
+  void SetFrameMetrics(const FrameMetrics& metrics) {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    mFrameMetrics = metrics;
+  }
+
+  FrameMetrics& GetFrameMetrics() {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    return mFrameMetrics;
+  }
+
+  const FrameMetrics& GetFrameMetrics() const {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    return mFrameMetrics;
+  }
+
+  using AsyncPanZoomController::GetVelocityVector;
+
+  void AssertStateIsReset() const {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    EXPECT_EQ(NOTHING, mState);
+  }
+
+  void AssertStateIsFling() const {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    EXPECT_EQ(FLING, mState);
+  }
+
+  void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) {
+    while (AdvanceAnimations(mcc->Time())) {
+      mcc->AdvanceBy(aIncrement);
+    }
+  }
+
+  bool SampleContentTransformForFrame(AsyncTransform* aOutTransform,
+                                      ParentLayerPoint& aScrollOffset,
+                                      const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) {
+    mcc->AdvanceBy(aIncrement);
+    bool ret = AdvanceAnimations(mcc->Time());
+    AsyncPanZoomController::SampleContentTransformForFrame(
+      aOutTransform, aScrollOffset);
+    return ret;
+  }
+
+  void SetWaitForMainThread() {
+    mWaitForMainThread = true;
+  }
+
+  static TimeStamp GetStartupTime() {
+    static TimeStamp sStartupTime = TimeStamp::Now();
+    return sStartupTime;
+  }
+
+private:
+  bool mWaitForMainThread;
+  MockContentControllerDelayed* mcc;
+};
+
+AsyncPanZoomController*
+TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
+                                     GeckoContentController* aController)
+{
+  MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController);
+  return new TestAsyncPanZoomController(aLayersId, mcc, this,
+      AsyncPanZoomController::USE_GESTURE_DETECTOR);
+}
+
+FrameMetrics
+TestFrameMetrics()
+{
+  FrameMetrics fm;
+
+  fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
+  fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10));
+  fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10));
+  fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
+
+  return fm;
+}
+
+uint32_t
+MillisecondsSinceStartup(TimeStamp aTime)
+{
+  return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds();
+}
+
+#endif // mozilla_layers_APZTestCommon_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/InputUtils.h
@@ -0,0 +1,466 @@
+/* -*- 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_InputUtils_h
+#define mozilla_layers_InputUtils_h
+
+/**
+ * Defines a set of utility functions for generating input events
+ * to an APZC/APZCTM during APZ gtests.
+ */
+
+#include "APZTestCommon.h"
+
+/* The InputReceiver template parameter used in the helper functions below needs
+ * to be a class that implements functions with the signatures:
+ * nsEventStatus ReceiveInputEvent(const InputData& aEvent,
+ *                                 ScrollableLayerGuid* aGuid,
+ *                                 uint64_t* aOutInputBlockId);
+ * void SetAllowedTouchBehavior(uint64_t aInputBlockId,
+ *                              const nsTArray<uint32_t>& aBehaviours);
+ * The classes that currently implement these are APZCTreeManager and
+ * TestAsyncPanZoomController. Using this template allows us to test individual
+ * APZC instances in isolation and also an entire APZ tree, while using the same
+ * code to dispatch input events.
+ */
+
+// Some helper functions for constructing input event objects suitable to be
+// passed either to an APZC (which expects an transformed point), or to an APZTM
+// (which expects an untransformed point). We handle both cases by setting both
+// the transformed and untransformed fields to the same value.
+SingleTouchData
+CreateSingleTouchData(int32_t aIdentifier, int aX, int aY)
+{
+  SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0);
+  touch.mLocalScreenPoint = ParentLayerPoint(aX, aY);
+  return touch;
+}
+
+PinchGestureInput
+CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType,
+                        int aFocusX, int aFocusY,
+                        float aCurrentSpan, float aPreviousSpan)
+{
+  PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
+                           aCurrentSpan, aPreviousSpan, 0);
+  result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY);
+  return result;
+}
+
+template<class InputReceiver>
+void
+SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget,
+                               uint64_t aInputBlockId,
+                               int touchPoints = 1)
+{
+  nsTArray<uint32_t> defaultBehaviors;
+  // use the default value where everything is allowed
+  for (int i = 0; i < touchPoints; i++) {
+    defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
+                                 | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
+                                 | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM
+                                 | mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM);
+  }
+  aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors);
+}
+
+
+MultiTouchInput
+CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime)
+{
+  return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0);
+}
+
+template<class InputReceiver>
+nsEventStatus
+TouchDown(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
+{
+  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
+  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
+  return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId);
+}
+
+template<class InputReceiver>
+nsEventStatus
+TouchMove(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
+{
+  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
+  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
+  return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
+}
+
+template<class InputReceiver>
+nsEventStatus
+TouchUp(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
+{
+  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
+  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
+  return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
+}
+
+template<class InputReceiver>
+void
+Tap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
+    TimeDuration aTapLength,
+    nsEventStatus (*aOutEventStatuses)[2] = nullptr,
+    uint64_t* aOutInputBlockId = nullptr)
+{
+  // Even if the caller doesn't care about the block id, we need it to set the
+  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
+  uint64_t blockId;
+  if (!aOutInputBlockId) {
+    aOutInputBlockId = &blockId;
+  }
+
+  nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+  aMcc->AdvanceBy(aTapLength);
+
+  // If touch-action is enabled then simulate the allowed touch behaviour
+  // notification that the main thread is supposed to deliver.
+  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
+    SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
+  }
+
+  status = TouchUp(aTarget, aX, aY, aMcc->Time());
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+}
+
+template<class InputReceiver>
+void
+TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
+    MockContentControllerDelayed* aMcc, TimeDuration aTapLength)
+{
+  nsEventStatus statuses[2];
+  Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
+}
+
+template<class InputReceiver>
+void
+Pan(const RefPtr<InputReceiver>& aTarget,
+    MockContentControllerDelayed* aMcc,
+    const ScreenPoint& aTouchStart,
+    const ScreenPoint& aTouchEnd,
+    bool aKeepFingerDown = false,
+    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
+    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+    uint64_t* aOutInputBlockId = nullptr)
+{
+  // Reduce the touch start and move tolerance to a tiny value.
+  // We can't use a scoped pref because this value might be read at some later
+  // time when the events are actually processed, rather than when we deliver
+  // them.
+  gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f);
+  gfxPrefs::SetAPZTouchMoveTolerance(0.0f);
+  const int OVERCOME_TOUCH_TOLERANCE = 1;
+
+  const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50);
+
+  // Even if the caller doesn't care about the block id, we need it to set the
+  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
+  uint64_t blockId;
+  if (!aOutInputBlockId) {
+    aOutInputBlockId = &blockId;
+  }
+
+  // Make sure the move is large enough to not be handled as a tap
+  nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+
+  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
+
+  // Allowed touch behaviours must be set after sending touch-start.
+  if (status != nsEventStatus_eConsumeNoDefault) {
+    if (aAllowedTouchBehaviors) {
+      EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length());
+      aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
+    } else if (gfxPrefs::TouchActionEnabled()) {
+      SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
+    }
+  }
+
+  status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time());
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+
+  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
+
+  status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = status;
+  }
+
+  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
+
+  if (!aKeepFingerDown) {
+    status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
+  } else {
+    status = nsEventStatus_eIgnore;
+  }
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[3] = status;
+  }
+
+  // Don't increment the time here. Animations started on touch-up, such as
+  // flings, are affected by elapsed time, and we want to be able to sample
+  // them immediately after they start, without time having elapsed.
+}
+
+// A version of Pan() that only takes y coordinates rather than (x, y) points
+// for the touch start and end points, and uses 10 for the x coordinates.
+// This is for convenience, as most tests only need to pan in one direction.
+template<class InputReceiver>
+void
+Pan(const RefPtr<InputReceiver>& aTarget,
+    MockContentControllerDelayed* aMcc,
+    int aTouchStartY,
+    int aTouchEndY,
+    bool aKeepFingerDown = false,
+    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
+    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+    uint64_t* aOutInputBlockId = nullptr)
+{
+  ::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY),
+      aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId);
+}
+
+/*
+ * Dispatches mock touch events to the apzc and checks whether apzc properly
+ * consumed them and triggered scrolling behavior.
+ */
+template<class InputReceiver>
+void
+PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
+                  MockContentControllerDelayed* aMcc,
+                  int aTouchStartY,
+                  int aTouchEndY,
+                  bool aExpectConsumed,
+                  nsTArray<uint32_t>* aAllowedTouchBehaviors,
+                  uint64_t* aOutInputBlockId = nullptr)
+{
+  nsEventStatus statuses[4]; // down, move, move, up
+  Pan(aTarget, aMcc, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId);
+
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
+
+  nsEventStatus touchMoveStatus;
+  if (aExpectConsumed) {
+    touchMoveStatus = nsEventStatus_eConsumeDoDefault;
+  } else {
+    touchMoveStatus = nsEventStatus_eIgnore;
+  }
+  EXPECT_EQ(touchMoveStatus, statuses[1]);
+  EXPECT_EQ(touchMoveStatus, statuses[2]);
+}
+
+void
+ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc,
+               MockContentControllerDelayed* aMcc,
+               int aTouchStartY,
+               int aTouchEndY,
+               uint64_t* aOutInputBlockId = nullptr)
+{
+  Pan(aApzc, aMcc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId);
+  aApzc->CancelAnimation();
+}
+
+template<class InputReceiver>
+void
+PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget,
+                    int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale,
+                    nsEventStatus (*aOutEventStatuses)[3] = nullptr)
+{
+  nsEventStatus actualStatus = aTarget->ReceiveInputEvent(
+      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
+                              aFocusX, aFocusY, 10.0, 10.0),
+      nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = actualStatus;
+  }
+  actualStatus = aTarget->ReceiveInputEvent(
+      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
+                              aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0),
+      nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = actualStatus;
+  }
+  actualStatus = aTarget->ReceiveInputEvent(
+      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
+                              // note: negative values here tell APZC
+                              //       not to turn the pinch into a pan
+                              aFocusX, aFocusY, -1.0, -1.0),
+      nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = actualStatus;
+  }
+}
+
+template<class InputReceiver>
+void
+PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
+                                  int aFocusX, int aFocusY, float aScale,
+                                  bool aShouldTriggerPinch)
+{
+  nsEventStatus statuses[3];  // scalebegin, scale, scaleend
+  PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses);
+
+  nsEventStatus expectedStatus = aShouldTriggerPinch
+      ? nsEventStatus_eConsumeNoDefault
+      : nsEventStatus_eIgnore;
+  EXPECT_EQ(expectedStatus, statuses[0]);
+  EXPECT_EQ(expectedStatus, statuses[1]);
+}
+
+template<class InputReceiver>
+void
+PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
+                    int aFocusX, int aFocusY, float aScale,
+                    int& inputId,
+                    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
+                    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+                    uint64_t* aOutInputBlockId = nullptr)
+{
+  // Having pinch coordinates in float type may cause problems with high-precision scale values
+  // since SingleTouchData accepts integer value. But for trivial tests it should be ok.
+  float pinchLength = 100.0;
+  float pinchLengthScaled = pinchLength * aScale;
+
+  // Even if the caller doesn't care about the block id, we need it to set the
+  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
+  uint64_t blockId;
+  if (!aOutInputBlockId) {
+    aOutInputBlockId = &blockId;
+  }
+
+  MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY));
+  mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY));
+  nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+
+  if (aAllowedTouchBehaviors) {
+    EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length());
+    aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
+  } else if (gfxPrefs::TouchActionEnabled()) {
+    SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2);
+  }
+
+  MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY));
+  mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY));
+  status = aTarget->ReceiveInputEvent(mtiMove1, nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+
+  MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
+  mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
+  status = aTarget->ReceiveInputEvent(mtiMove2, nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = status;
+  }
+
+  MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
+  mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
+  status = aTarget->ReceiveInputEvent(mtiEnd, nullptr);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[3] = status;
+  }
+
+  inputId += 2;
+}
+
+template<class InputReceiver>
+void
+PinchWithTouchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
+                                  int aFocusX, int aFocusY, float aScale,
+                                  int& inputId, bool aShouldTriggerPinch,
+                                  nsTArray<uint32_t>* aAllowedTouchBehaviors)
+{
+  nsEventStatus statuses[4];  // down, move, move, up
+  PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses);
+
+  nsEventStatus expectedMoveStatus = aShouldTriggerPinch
+      ? nsEventStatus_eConsumeDoDefault
+      : nsEventStatus_eIgnore;
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
+  EXPECT_EQ(expectedMoveStatus, statuses[1]);
+  EXPECT_EQ(expectedMoveStatus, statuses[2]);
+}
+
+template<class InputReceiver>
+void
+DoubleTap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
+          nsEventStatus (*aOutEventStatuses)[4] = nullptr,
+          uint64_t (*aOutInputBlockIds)[2] = nullptr)
+{
+  uint64_t blockId;
+  nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[0] = status;
+  }
+  if (aOutInputBlockIds) {
+    (*aOutInputBlockIds)[0] = blockId;
+  }
+  aMcc->AdvanceByMillis(10);
+
+  // If touch-action is enabled then simulate the allowed touch behaviour
+  // notification that the main thread is supposed to deliver.
+  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
+    SetDefaultAllowedTouchBehavior(aTarget, blockId);
+  }
+
+  status = TouchUp(aTarget, aX, aY, aMcc->Time());
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[1] = status;
+  }
+  aMcc->AdvanceByMillis(10);
+  status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[2] = status;
+  }
+  if (aOutInputBlockIds) {
+    (*aOutInputBlockIds)[1] = blockId;
+  }
+  aMcc->AdvanceByMillis(10);
+
+  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
+    SetDefaultAllowedTouchBehavior(aTarget, blockId);
+  }
+
+  status = TouchUp(aTarget, aX, aY, aMcc->Time());
+  if (aOutEventStatuses) {
+    (*aOutEventStatuses)[3] = status;
+  }
+}
+
+template<class InputReceiver>
+void
+DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
+    MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr)
+{
+  nsEventStatus statuses[4];
+  DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
+}
+
+#endif // mozilla_layers_InputUtils_h
deleted file mode 100644
--- a/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp
+++ /dev/null
@@ -1,3489 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-#include "gtest/gtest.h"
-#include "gmock/gmock.h"
-
-#include "mozilla/Attributes.h"
-#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
-#include "mozilla/layers/GeckoContentController.h"
-#include "mozilla/layers/CompositorParent.h"
-#include "mozilla/layers/APZCTreeManager.h"
-#include "mozilla/layers/LayerMetricsWrapper.h"
-#include "mozilla/layers/APZThreadUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "apz/src/AsyncPanZoomController.h"
-#include "apz/src/HitTestingTreeNode.h"
-#include "base/task.h"
-#include "Layers.h"
-#include "TestLayers.h"
-#include "UnitTransforms.h"
-#include "gfxPrefs.h"
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::layers;
-using ::testing::_;
-using ::testing::NiceMock;
-using ::testing::AtLeast;
-using ::testing::AtMost;
-using ::testing::MockFunction;
-using ::testing::InSequence;
-
-class Task;
-
-template<class T>
-class ScopedGfxPref {
-public:
-  ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal)
-    : mSetPrefFunc(aSetPrefFunc)
-  {
-    mOldVal = aGetPrefFunc();
-    aSetPrefFunc(aVal);
-  }
-
-  ~ScopedGfxPref() {
-    mSetPrefFunc(mOldVal);
-  }
-
-private:
-  void (*mSetPrefFunc)(T);
-  T mOldVal;
-};
-
-#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \
-  ScopedGfxPref<prefType> pref_##prefBase( \
-    &(gfxPrefs::prefBase), \
-    &(gfxPrefs::Set##prefBase), \
-    prefValue)
-
-class MockContentController : public GeckoContentController {
-public:
-  MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
-  MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
-  MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
-  MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
-  MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
-  MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
-  MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
-  MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
-  MOCK_METHOD0(NotifyFlushComplete, void());
-};
-
-class MockContentControllerDelayed : public MockContentController {
-public:
-  MockContentControllerDelayed()
-    : mTime(TimeStamp::Now())
-  {
-  }
-
-  const TimeStamp& Time() {
-    return mTime;
-  }
-
-  void AdvanceByMillis(int aMillis) {
-    AdvanceBy(TimeDuration::FromMilliseconds(aMillis));
-  }
-
-  void AdvanceBy(const TimeDuration& aIncrement) {
-    TimeStamp target = mTime + aIncrement;
-    while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) {
-      RunNextDelayedTask();
-    }
-    mTime = target;
-  }
-
-  void PostDelayedTask(Task* aTask, int aDelayMs) {
-    TimeStamp runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs);
-    int insIndex = mTaskQueue.Length();
-    while (insIndex > 0) {
-      if (mTaskQueue[insIndex - 1].second <= runAtTime) {
-        break;
-      }
-      insIndex--;
-    }
-    mTaskQueue.InsertElementAt(insIndex, std::make_pair(aTask, runAtTime));
-  }
-
-  // Run all the tasks in the queue, returning the number of tasks
-  // run. Note that if a task queues another task while running, that
-  // new task will not be run. Therefore, there may be still be tasks
-  // in the queue after this function is called. Only when the return
-  // value is 0 is the queue guaranteed to be empty.
-  int RunThroughDelayedTasks() {
-    nsTArray<std::pair<Task*, TimeStamp>> runQueue;
-    runQueue.SwapElements(mTaskQueue);
-    int numTasks = runQueue.Length();
-    for (int i = 0; i < numTasks; i++) {
-      mTime = runQueue[i].second;
-      runQueue[i].first->Run();
-
-      // Deleting the task is important in order to release the reference to
-      // the callee object.
-      delete runQueue[i].first;
-    }
-    return numTasks;
-  }
-
-private:
-  void RunNextDelayedTask() {
-    std::pair<Task*, TimeStamp> next = mTaskQueue[0];
-    mTaskQueue.RemoveElementAt(0);
-    mTime = next.second;
-    next.first->Run();
-    // Deleting the task is important in order to release the reference to
-    // the callee object.
-    delete next.first;
-  }
-
-  // The following array is sorted by timestamp (tasks are inserted in order by
-  // timestamp).
-  nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue;
-  TimeStamp mTime;
-};
-
-class TestAPZCTreeManager : public APZCTreeManager {
-public:
-  explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {}
-
-  RefPtr<InputQueue> GetInputQueue() const {
-    return mInputQueue;
-  }
-
-protected:
-  AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
-                                          GeckoContentController* aController) override;
-
-  TimeStamp GetFrameTime() override {
-    return mcc->Time();
-  }
-
-private:
-  RefPtr<MockContentControllerDelayed> mcc;
-};
-
-class TestAsyncPanZoomController : public AsyncPanZoomController {
-public:
-  TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc,
-                             TestAPZCTreeManager* aTreeManager,
-                             GestureBehavior aBehavior = DEFAULT_GESTURES)
-    : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(),
-        aMcc, aBehavior)
-    , mWaitForMainThread(false)
-    , mcc(aMcc)
-  {}
-
-  nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) {
-    // This is a function whose signature matches exactly the ReceiveInputEvent
-    // on APZCTreeManager. This allows us to templates for functions like
-    // TouchDown, TouchUp, etc so that we can reuse the code for dispatching
-    // events into both APZC and APZCTM.
-    return ReceiveInputEvent(aEvent, aOutInputBlockId);
-  }
-
-  nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) {
-    return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId);
-  }
-
-  void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) {
-    GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
-  }
-
-  void ConfirmTarget(uint64_t aInputBlockId) {
-    RefPtr<AsyncPanZoomController> target = this;
-    GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target);
-  }
-
-  void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
-    GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
-  }
-
-  void SetFrameMetrics(const FrameMetrics& metrics) {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    mFrameMetrics = metrics;
-  }
-
-  FrameMetrics& GetFrameMetrics() {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    return mFrameMetrics;
-  }
-
-  const FrameMetrics& GetFrameMetrics() const {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    return mFrameMetrics;
-  }
-
-  using AsyncPanZoomController::GetVelocityVector;
-
-  void AssertStateIsReset() const {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    EXPECT_EQ(NOTHING, mState);
-  }
-
-  void AssertStateIsFling() const {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    EXPECT_EQ(FLING, mState);
-  }
-
-  void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) {
-    while (AdvanceAnimations(mcc->Time())) {
-      mcc->AdvanceBy(aIncrement);
-    }
-  }
-
-  bool SampleContentTransformForFrame(AsyncTransform* aOutTransform,
-                                      ParentLayerPoint& aScrollOffset,
-                                      const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) {
-    mcc->AdvanceBy(aIncrement);
-    bool ret = AdvanceAnimations(mcc->Time());
-    AsyncPanZoomController::SampleContentTransformForFrame(
-      aOutTransform, aScrollOffset);
-    return ret;
-  }
-
-  void SetWaitForMainThread() {
-    mWaitForMainThread = true;
-  }
-
-  static TimeStamp GetStartupTime() {
-    static TimeStamp sStartupTime = TimeStamp::Now();
-    return sStartupTime;
-  }
-
-private:
-  bool mWaitForMainThread;
-  MockContentControllerDelayed* mcc;
-};
-
-AsyncPanZoomController*
-TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
-                                     GeckoContentController* aController)
-{
-  MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController);
-  return new TestAsyncPanZoomController(aLayersId, mcc, this,
-      AsyncPanZoomController::USE_GESTURE_DETECTOR);
-}
-
-static FrameMetrics
-TestFrameMetrics()
-{
-  FrameMetrics fm;
-
-  fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
-  fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10));
-  fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10));
-  fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
-
-  return fm;
-}
-
-class APZCBasicTester : public ::testing::Test {
-public:
-  explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
-    : mGestureBehavior(aGestureBehavior)
-  {
-  }
-
-protected:
-  virtual void SetUp()
-  {
-    gfxPrefs::GetSingleton();
-    APZThreadUtils::SetThreadAssertionsEnabled(false);
-    APZThreadUtils::SetControllerThread(MessageLoop::current());
-
-    mcc = new NiceMock<MockContentControllerDelayed>();
-    tm = new TestAPZCTreeManager(mcc);
-    apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
-    apzc->SetFrameMetrics(TestFrameMetrics());
-  }
-
-  /**
-   * Get the APZC's scroll range in CSS pixels.
-   */
-  CSSRect GetScrollRange() const
-  {
-    const FrameMetrics& metrics = apzc->GetFrameMetrics();
-    return CSSRect(
-        metrics.GetScrollableRect().TopLeft(),
-        metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels());
-  }
-
-  virtual void TearDown()
-  {
-    while (mcc->RunThroughDelayedTasks());
-    apzc->Destroy();
-  }
-
-  void MakeApzcWaitForMainThread()
-  {
-    apzc->SetWaitForMainThread();
-  }
-
-  void MakeApzcZoomable()
-  {
-    apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f)));
-  }
-
-  void MakeApzcUnzoomable()
-  {
-    apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f)));
-  }
-
-  void PanIntoOverscroll();
-
-  /**
-   * Sample animations once, 1 ms later than the last sample.
-   */
-  void SampleAnimationOnce()
-  {
-    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    mcc->AdvanceBy(increment);
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  }
-
-  /**
-   * Sample animations until we recover from overscroll.
-   * @param aExpectedScrollOffset the expected reported scroll offset
-   *                              throughout the animation
-   */
-  void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset)
-  {
-    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
-    bool recoveredFromOverscroll = false;
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) {
-      // The reported scroll offset should be the same throughout.
-      EXPECT_EQ(aExpectedScrollOffset, pointOut);
-
-      // Trigger computation of the overscroll tranform, to make sure
-      // no assetions fire during the calculation.
-      apzc->GetOverscrollTransform();
-
-      if (!apzc->IsOverscrolled()) {
-        recoveredFromOverscroll = true;
-      }
-
-      mcc->AdvanceBy(increment);
-    }
-    EXPECT_TRUE(recoveredFromOverscroll);
-    apzc->AssertStateIsReset();
-  }
-
-  void TestOverscroll();
-
-  AsyncPanZoomController::GestureBehavior mGestureBehavior;
-  RefPtr<MockContentControllerDelayed> mcc;
-  RefPtr<TestAPZCTreeManager> tm;
-  RefPtr<TestAsyncPanZoomController> apzc;
-};
-
-class APZCGestureDetectorTester : public APZCBasicTester {
-public:
-  APZCGestureDetectorTester()
-    : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
-  {
-  }
-
-protected:
-  FrameMetrics GetPinchableFrameMetrics()
-  {
-    FrameMetrics fm;
-    fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
-    fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
-    fm.SetScrollOffset(CSSPoint(300, 300));
-    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
-    // APZC only allows zooming on the root scrollable frame.
-    fm.SetIsRootContent(true);
-    // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
-    return fm;
-  }
-
-};
-
-/* The InputReceiver template parameter used in the helper functions below needs
- * to be a class that implements functions with the signatures:
- * nsEventStatus ReceiveInputEvent(const InputData& aEvent,
- *                                 ScrollableLayerGuid* aGuid,
- *                                 uint64_t* aOutInputBlockId);
- * void SetAllowedTouchBehavior(uint64_t aInputBlockId,
- *                              const nsTArray<uint32_t>& aBehaviours);
- * The classes that currently implement these are APZCTreeManager and
- * TestAsyncPanZoomController. Using this template allows us to test individual
- * APZC instances in isolation and also an entire APZ tree, while using the same
- * code to dispatch input events.
- */
-
-// Some helper functions for constructing input event objects suitable to be
-// passed either to an APZC (which expects an transformed point), or to an APZTM
-// (which expects an untransformed point). We handle both cases by setting both
-// the transformed and untransformed fields to the same value.
-static SingleTouchData
-CreateSingleTouchData(int32_t aIdentifier, int aX, int aY)
-{
-  SingleTouchData touch(aIdentifier, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0);
-  touch.mLocalScreenPoint = ParentLayerPoint(aX, aY);
-  return touch;
-}
-static PinchGestureInput
-CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType,
-                        int aFocusX, int aFocusY,
-                        float aCurrentSpan, float aPreviousSpan)
-{
-  PinchGestureInput result(aType, 0, TimeStamp(), ScreenPoint(aFocusX, aFocusY),
-                           aCurrentSpan, aPreviousSpan, 0);
-  result.mLocalFocusPoint = ParentLayerPoint(aFocusX, aFocusY);
-  return result;
-}
-
-template<class InputReceiver> static void
-SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget,
-                               uint64_t aInputBlockId,
-                               int touchPoints = 1)
-{
-  nsTArray<uint32_t> defaultBehaviors;
-  // use the default value where everything is allowed
-  for (int i = 0; i < touchPoints; i++) {
-    defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
-                                 | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
-                                 | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM
-                                 | mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM);
-  }
-  aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors);
-}
-
-static uint32_t
-MillisecondsSinceStartup(TimeStamp aTime)
-{
-  return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds();
-}
-
-static MultiTouchInput
-CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime)
-{
-  return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0);
-}
-
-template<class InputReceiver> static nsEventStatus
-TouchDown(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
-{
-  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
-  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
-  return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId);
-}
-
-template<class InputReceiver> static nsEventStatus
-TouchMove(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
-{
-  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
-  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
-  return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
-}
-
-template<class InputReceiver> static nsEventStatus
-TouchUp(const RefPtr<InputReceiver>& aTarget, int aX, int aY, TimeStamp aTime)
-{
-  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
-  mti.mTouches.AppendElement(CreateSingleTouchData(0, aX, aY));
-  return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
-}
-
-template<class InputReceiver> static void
-Tap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
-    TimeDuration aTapLength,
-    nsEventStatus (*aOutEventStatuses)[2] = nullptr,
-    uint64_t* aOutInputBlockId = nullptr)
-{
-  // Even if the caller doesn't care about the block id, we need it to set the
-  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
-  uint64_t blockId;
-  if (!aOutInputBlockId) {
-    aOutInputBlockId = &blockId;
-  }
-
-  nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), aOutInputBlockId);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[0] = status;
-  }
-  aMcc->AdvanceBy(aTapLength);
-
-  // If touch-action is enabled then simulate the allowed touch behaviour
-  // notification that the main thread is supposed to deliver.
-  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
-    SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
-  }
-
-  status = TouchUp(aTarget, aX, aY, aMcc->Time());
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[1] = status;
-  }
-}
-
-template<class InputReceiver> static void
-TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
-    MockContentControllerDelayed* aMcc, TimeDuration aTapLength)
-{
-  nsEventStatus statuses[2];
-  Tap(aTarget, aX, aY, aMcc, aTapLength, &statuses);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
-}
-
-template<class InputReceiver> static void
-Pan(const RefPtr<InputReceiver>& aTarget,
-    MockContentControllerDelayed* aMcc,
-    const ScreenPoint& aTouchStart,
-    const ScreenPoint& aTouchEnd,
-    bool aKeepFingerDown = false,
-    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
-    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
-    uint64_t* aOutInputBlockId = nullptr)
-{
-  // Reduce the touch start and move tolerance to a tiny value.
-  // We can't use a scoped pref because this value might be read at some later
-  // time when the events are actually processed, rather than when we deliver
-  // them.
-  gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f);
-  gfxPrefs::SetAPZTouchMoveTolerance(0.0f);
-  const int OVERCOME_TOUCH_TOLERANCE = 1;
-
-  const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50);
-
-  // Even if the caller doesn't care about the block id, we need it to set the
-  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
-  uint64_t blockId;
-  if (!aOutInputBlockId) {
-    aOutInputBlockId = &blockId;
-  }
-
-  // Make sure the move is large enough to not be handled as a tap
-  nsEventStatus status = TouchDown(aTarget, aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE, aMcc->Time(), aOutInputBlockId);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[0] = status;
-  }
-
-  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
-
-  // Allowed touch behaviours must be set after sending touch-start.
-  if (status != nsEventStatus_eConsumeNoDefault) {
-    if (aAllowedTouchBehaviors) {
-      EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length());
-      aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
-    } else if (gfxPrefs::TouchActionEnabled()) {
-      SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
-    }
-  }
-
-  status = TouchMove(aTarget, aTouchStart.x, aTouchStart.y, aMcc->Time());
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[1] = status;
-  }
-
-  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
-
-  status = TouchMove(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[2] = status;
-  }
-
-  aMcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
-
-  if (!aKeepFingerDown) {
-    status = TouchUp(aTarget, aTouchEnd.x, aTouchEnd.y, aMcc->Time());
-  } else {
-    status = nsEventStatus_eIgnore;
-  }
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[3] = status;
-  }
-
-  // Don't increment the time here. Animations started on touch-up, such as
-  // flings, are affected by elapsed time, and we want to be able to sample
-  // them immediately after they start, without time having elapsed.
-}
-
-// A version of Pan() that only takes y coordinates rather than (x, y) points
-// for the touch start and end points, and uses 10 for the x coordinates.
-// This is for convenience, as most tests only need to pan in one direction.
-template<class InputReceiver> static void
-Pan(const RefPtr<InputReceiver>& aTarget,
-    MockContentControllerDelayed* aMcc,
-    int aTouchStartY,
-    int aTouchEndY,
-    bool aKeepFingerDown = false,
-    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
-    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
-    uint64_t* aOutInputBlockId = nullptr)
-{
-  ::Pan(aTarget, aMcc, ScreenPoint(10, aTouchStartY), ScreenPoint(10, aTouchEndY),
-      aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId);
-}
-
-/*
- * Dispatches mock touch events to the apzc and checks whether apzc properly
- * consumed them and triggered scrolling behavior.
- */
-template<class InputReceiver> static void
-PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
-                  MockContentControllerDelayed* aMcc,
-                  int aTouchStartY,
-                  int aTouchEndY,
-                  bool aExpectConsumed,
-                  nsTArray<uint32_t>* aAllowedTouchBehaviors,
-                  uint64_t* aOutInputBlockId = nullptr)
-{
-  nsEventStatus statuses[4]; // down, move, move, up
-  Pan(aTarget, aMcc, aTouchStartY, aTouchEndY, false, aAllowedTouchBehaviors, &statuses, aOutInputBlockId);
-
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
-
-  nsEventStatus touchMoveStatus;
-  if (aExpectConsumed) {
-    touchMoveStatus = nsEventStatus_eConsumeDoDefault;
-  } else {
-    touchMoveStatus = nsEventStatus_eIgnore;
-  }
-  EXPECT_EQ(touchMoveStatus, statuses[1]);
-  EXPECT_EQ(touchMoveStatus, statuses[2]);
-}
-
-static void
-ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc,
-               MockContentControllerDelayed* aMcc,
-               int aTouchStartY,
-               int aTouchEndY,
-               uint64_t* aOutInputBlockId = nullptr)
-{
-  Pan(aApzc, aMcc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId);
-  aApzc->CancelAnimation();
-}
-
-template<class InputReceiver> static void
-PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget,
-                    int aFocusX, int aFocusY, int aSecondFocusX, int aSecondFocusY, float aScale,
-                    nsEventStatus (*aOutEventStatuses)[3] = nullptr)
-{
-  nsEventStatus actualStatus = aTarget->ReceiveInputEvent(
-      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
-                              aFocusX, aFocusY, 10.0, 10.0),
-      nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[0] = actualStatus;
-  }
-  actualStatus = aTarget->ReceiveInputEvent(
-      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
-                              aSecondFocusX, aSecondFocusY, 10.0 * aScale, 10.0),
-      nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[1] = actualStatus;
-  }
-  actualStatus = aTarget->ReceiveInputEvent(
-      CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
-                              // note: negative values here tell APZC
-                              //       not to turn the pinch into a pan
-                              aFocusX, aFocusY, -1.0, -1.0),
-      nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[2] = actualStatus;
-  }
-}
-
-template<class InputReceiver> static void
-PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
-                                  int aFocusX, int aFocusY, float aScale,
-                                  bool aShouldTriggerPinch)
-{
-  nsEventStatus statuses[3];  // scalebegin, scale, scaleend
-  PinchWithPinchInput(aTarget, aFocusX, aFocusY, aFocusX, aFocusY, aScale, &statuses);
-
-  nsEventStatus expectedStatus = aShouldTriggerPinch
-      ? nsEventStatus_eConsumeNoDefault
-      : nsEventStatus_eIgnore;
-  EXPECT_EQ(expectedStatus, statuses[0]);
-  EXPECT_EQ(expectedStatus, statuses[1]);
-}
-
-template<class InputReceiver> static void
-PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
-                    int aFocusX, int aFocusY, float aScale,
-                    int& inputId,
-                    nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
-                    nsEventStatus (*aOutEventStatuses)[4] = nullptr,
-                    uint64_t* aOutInputBlockId = nullptr)
-{
-  // Having pinch coordinates in float type may cause problems with high-precision scale values
-  // since SingleTouchData accepts integer value. But for trivial tests it should be ok.
-  float pinchLength = 100.0;
-  float pinchLengthScaled = pinchLength * aScale;
-
-  // Even if the caller doesn't care about the block id, we need it to set the
-  // allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
-  uint64_t blockId;
-  if (!aOutInputBlockId) {
-    aOutInputBlockId = &blockId;
-  }
-
-  MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX, aFocusY));
-  mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX, aFocusY));
-  nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[0] = status;
-  }
-
-  if (aAllowedTouchBehaviors) {
-    EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length());
-    aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
-  } else if (gfxPrefs::TouchActionEnabled()) {
-    SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2);
-  }
-
-  MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLength, aFocusY));
-  mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLength, aFocusY));
-  status = aTarget->ReceiveInputEvent(mtiMove1, nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[1] = status;
-  }
-
-  MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
-  mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
-  status = aTarget->ReceiveInputEvent(mtiMove2, nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[2] = status;
-  }
-
-  MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocusX - pinchLengthScaled, aFocusY));
-  mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocusX + pinchLengthScaled, aFocusY));
-  status = aTarget->ReceiveInputEvent(mtiEnd, nullptr);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[3] = status;
-  }
-
-  inputId += 2;
-}
-
-template<class InputReceiver> static void
-PinchWithTouchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
-                                  int aFocusX, int aFocusY, float aScale,
-                                  int& inputId, bool aShouldTriggerPinch,
-                                  nsTArray<uint32_t>* aAllowedTouchBehaviors)
-{
-  nsEventStatus statuses[4];  // down, move, move, up
-  PinchWithTouchInput(aTarget, aFocusX, aFocusY, aScale, inputId, aAllowedTouchBehaviors, &statuses);
-
-  nsEventStatus expectedMoveStatus = aShouldTriggerPinch
-      ? nsEventStatus_eConsumeDoDefault
-      : nsEventStatus_eIgnore;
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
-  EXPECT_EQ(expectedMoveStatus, statuses[1]);
-  EXPECT_EQ(expectedMoveStatus, statuses[2]);
-}
-
-class APZCPinchTester : public APZCBasicTester {
-public:
-  explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
-    : APZCBasicTester(aGestureBehavior)
-  {
-  }
-
-protected:
-  FrameMetrics GetPinchableFrameMetrics()
-  {
-    FrameMetrics fm;
-    fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
-    fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
-    fm.SetScrollOffset(CSSPoint(300, 300));
-    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
-    // APZC only allows zooming on the root scrollable frame.
-    fm.SetIsRootContent(true);
-    // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
-    return fm;
-  }
-
-  void DoPinchTest(bool aShouldTriggerPinch,
-                   nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
-  {
-    apzc->SetFrameMetrics(GetPinchableFrameMetrics());
-    MakeApzcZoomable();
-
-    if (aShouldTriggerPinch) {
-      // One repaint request for each gesture.
-      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
-    } else {
-      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
-    }
-
-    int touchInputId = 0;
-    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
-      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
-    } else {
-      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
-    }
-
-    FrameMetrics fm = apzc->GetFrameMetrics();
-
-    if (aShouldTriggerPinch) {
-      // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
-      EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale);
-      EXPECT_EQ(305, fm.GetScrollOffset().x);
-      EXPECT_EQ(310, fm.GetScrollOffset().y);
-    } else {
-      // The frame metrics should stay the same since touch-action:none makes
-      // apzc ignore pinch gestures.
-      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
-      EXPECT_EQ(300, fm.GetScrollOffset().x);
-      EXPECT_EQ(300, fm.GetScrollOffset().y);
-    }
-
-    // part 2 of the test, move to the top-right corner of the page and pinch and
-    // make sure we stay in the correct spot
-    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
-    fm.SetScrollOffset(CSSPoint(930, 5));
-    apzc->SetFrameMetrics(fm);
-    // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
-
-    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
-      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
-    } else {
-      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch);
-    }
-
-    fm = apzc->GetFrameMetrics();
-
-    if (aShouldTriggerPinch) {
-      // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
-      EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale);
-      EXPECT_EQ(880, fm.GetScrollOffset().x);
-      EXPECT_EQ(0, fm.GetScrollOffset().y);
-    } else {
-      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
-      EXPECT_EQ(930, fm.GetScrollOffset().x);
-      EXPECT_EQ(5, fm.GetScrollOffset().y);
-    }
-  }
-};
-
-class APZCPinchGestureDetectorTester : public APZCPinchTester {
-public:
-  APZCPinchGestureDetectorTester()
-    : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
-  {
-  }
-};
-
-TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoPinchTest(true);
-}
-
-TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoPinchTest(true);
-}
-
-TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
-                                   mozilla::layers::AllowedTouchBehavior::NONE };
-  DoPinchTest(false, &behaviors);
-}
-
-TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  nsTArray<uint32_t> behaviors;
-  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-  DoPinchTest(true, &behaviors);
-}
-
-TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  nsTArray<uint32_t> behaviors;
-  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-  DoPinchTest(false, &behaviors);
-}
-
-TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
-  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
-  apzc->SetFrameMetrics(originalMetrics);
-
-  MakeApzcWaitForMainThread();
-  MakeApzcZoomable();
-
-  int touchInputId = 0;
-  uint64_t blockId = 0;
-  PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId);
-
-  // Send the prevent-default notification for the touch block
-  apzc->ContentReceivedInputBlock(blockId, true);
-
-  // verify the metrics didn't change (i.e. the pinch was ignored)
-  FrameMetrics fm = apzc->GetFrameMetrics();
-  EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
-  EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
-  EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-
-  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
-  apzc->SetFrameMetrics(originalMetrics);
-
-  MakeApzcZoomable();
-
-  // Test parameters
-  float zoomAmount = 1.25;
-  float pinchLength = 100.0;
-  float pinchLengthScaled = pinchLength * zoomAmount;
-  int focusX = 250;
-  int focusY = 300;
-  int panDistance = 20;
-
-  int firstFingerId = 0;
-  int secondFingerId = firstFingerId + 1;
-
-  // Put fingers down
-  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY));
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Spread fingers out to enter the pinch state
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY));
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Do the actual pinch of 1.25x
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Verify that the zoom changed, just to make sure our code above did what it
-  // was supposed to.
-  FrameMetrics zoomedMetrics = apzc->GetFrameMetrics();
-  float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale;
-  EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom);
-
-  // Now we lift one finger...
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // ... and pan with the remaining finger. This pan just breaks through the
-  // distance threshold.
-  focusY += 40;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // This one does an actual pan of 20 pixels
-  focusY += panDistance;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Lift the remaining finger
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Verify that we scrolled
-  FrameMetrics finalMetrics = apzc->GetFrameMetrics();
-  EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y);
-
-  // Clear out any remaining fling animation and pending tasks
-  apzc->AdvanceAnimationsUntilEnd();
-  while (mcc->RunThroughDelayedTasks());
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, Pan_With_Tap) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-
-  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
-  apzc->SetFrameMetrics(originalMetrics);
-
-  // Making the APZC zoomable isn't really needed for the correct operation of
-  // this test, but it could help catch regressions where we accidentally enter
-  // a pinch state.
-  MakeApzcZoomable();
-
-  // Test parameters
-  int touchX = 250;
-  int touchY = 300;
-  int panDistance = 20;
-
-  int firstFingerId = 0;
-  int secondFingerId = firstFingerId + 1;
-
-  // Put finger down
-  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Start a pan, break through the threshold
-  touchY += 40;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Do an actual pan for a bit
-  touchY += panDistance;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Put a second finger down
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Lift the second finger
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Bust through the threshold again
-  touchY += 40;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Do some more actual panning
-  touchY += panDistance;
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Lift the first finger
-  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
-  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  // Verify that we scrolled
-  FrameMetrics finalMetrics = apzc->GetFrameMetrics();
-  float zoom = finalMetrics.GetZoom().ToScaleFactor().scale;
-  EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y);
-
-  // Clear out any remaining fling animation and pending tasks
-  apzc->AdvanceAnimationsUntilEnd();
-  while (mcc->RunThroughDelayedTasks());
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCBasicTester, Overzoom) {
-  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
-  FrameMetrics fm;
-  fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
-  fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
-  fm.SetScrollOffset(CSSPoint(10, 0));
-  fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
-  fm.SetIsRootContent(true);
-  apzc->SetFrameMetrics(fm);
-
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
-
-  PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true);
-
-  fm = apzc->GetFrameMetrics();
-  EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
-  // bug 936721 - PGO builds introduce rounding error so
-  // use a fuzzy match instead
-  EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
-  EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
-}
-
-TEST_F(APZCBasicTester, SimpleTransform) {
-  ParentLayerPoint pointOut;
-  AsyncTransform viewTransformOut;
-  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-
-  EXPECT_EQ(ParentLayerPoint(), pointOut);
-  EXPECT_EQ(AsyncTransform(), viewTransformOut);
-}
-
-
-TEST_F(APZCBasicTester, ComplexTransform) {
-  // This test assumes there is a page that gets rendered to
-  // two layers. In CSS pixels, the first layer is 50x50 and
-  // the second layer is 25x50. The widget scale factor is 3.0
-  // and the presShell resolution is 2.0. Therefore, these layers
-  // end up being 300x300 and 150x300 in layer pixels.
-  //
-  // The second (child) layer has an additional CSS transform that
-  // stretches it by 2.0 on the x-axis. Therefore, after applying
-  // CSS transforms, the two layers are the same size in screen
-  // pixels.
-  //
-  // The screen itself is 24x24 in screen pixels (therefore 4x4 in
-  // CSS pixels). The displayport is 1 extra CSS pixel on all
-  // sides.
-
-  RefPtr<TestAsyncPanZoomController> childApzc =
-      new TestAsyncPanZoomController(0, mcc, tm);
-
-  const char* layerTreeSyntax = "c(c)";
-  // LayerID                     0 1
-  nsIntRegion layerVisibleRegion[] = {
-    nsIntRegion(IntRect(0, 0, 300, 300)),
-    nsIntRegion(IntRect(0, 0, 150, 300)),
-  };
-  Matrix4x4 transforms[] = {
-    Matrix4x4(),
-    Matrix4x4(),
-  };
-  transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
-  transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
-
-  nsTArray<RefPtr<Layer> > layers;
-  RefPtr<LayerManager> lm;
-  RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
-
-  FrameMetrics metrics;
-  metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
-  metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
-  metrics.SetScrollOffset(CSSPoint(10, 10));
-  metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
-  metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
-  metrics.SetPresShellResolution(2.0f);
-  metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
-  metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
-  metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);
-
-  FrameMetrics childMetrics = metrics;
-  childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);
-
-  layers[0]->SetFrameMetrics(metrics);
-  layers[1]->SetFrameMetrics(childMetrics);
-
-  ParentLayerPoint pointOut;
-  AsyncTransform viewTransformOut;
-
-  // Both the parent and child layer should behave exactly the same here, because
-  // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code
-
-  // initial transform
-  apzc->SetFrameMetrics(metrics);
-  apzc->NotifyLayersUpdated(metrics, true, true);
-  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
-
-  childApzc->SetFrameMetrics(childMetrics);
-  childApzc->NotifyLayersUpdated(childMetrics, true, true);
-  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
-
-  // do an async scroll by 5 pixels and check the transform
-  metrics.ScrollBy(CSSPoint(5, 0));
-  apzc->SetFrameMetrics(metrics);
-  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
-
-  childMetrics.ScrollBy(CSSPoint(5, 0));
-  childApzc->SetFrameMetrics(childMetrics);
-  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
-
-  // do an async zoom of 1.5x and check the transform
-  metrics.ZoomBy(1.5f);
-  apzc->SetFrameMetrics(metrics);
-  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
-
-  childMetrics.ZoomBy(1.5f);
-  childApzc->SetFrameMetrics(childMetrics);
-  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
-  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
-
-  childApzc->Destroy();
-}
-
-TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) {
-  // set up APZ
-  apzc->SetFrameMetrics(GetPinchableFrameMetrics());
-  MakeApzcUnzoomable();
-
-  nsEventStatus statuses[3];  // scalebegin, scale, scaleend
-  PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses);
-
-  FrameMetrics fm = apzc->GetFrameMetrics();
-
-  // It starts from (300, 300), then moves the focus point from (250, 350) to
-  // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which
-  // causes the scroll offset to change by half of that (25, 25) pixels.
-  EXPECT_EQ(325, fm.GetScrollOffset().x);
-  EXPECT_EQ(325, fm.GetScrollOffset().y);
-  EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
-}
-
-class APZCPanningTester : public APZCBasicTester {
-protected:
-  void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
-  {
-    if (aShouldTriggerScroll) {
-      // One repaint request for each pan.
-      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
-    } else {
-      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
-    }
-
-    int touchStart = 50;
-    int touchEnd = 10;
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-
-    nsTArray<uint32_t> allowedTouchBehaviors;
-    allowedTouchBehaviors.AppendElement(aBehavior);
-
-    // Pan down
-    PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors);
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-
-    if (aShouldTriggerScroll) {
-      EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut);
-      EXPECT_NE(AsyncTransform(), viewTransformOut);
-    } else {
-      EXPECT_EQ(ParentLayerPoint(), pointOut);
-      EXPECT_EQ(AsyncTransform(), viewTransformOut);
-    }
-
-    // Clear the fling from the previous pan, or stopping it will
-    // consume the next touchstart
-    apzc->CancelAnimation();
-
-    // Pan back
-    PanAndCheckStatus(apzc, mcc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors);
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-
-    EXPECT_EQ(ParentLayerPoint(), pointOut);
-    EXPECT_EQ(AsyncTransform(), viewTransformOut);
-  }
-
-  void DoPanWithPreventDefaultTest()
-  {
-    MakeApzcWaitForMainThread();
-
-    int touchStart = 50;
-    int touchEnd = 10;
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    uint64_t blockId = 0;
-
-    // Pan down
-    nsTArray<uint32_t> allowedTouchBehaviors;
-    allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-    PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId);
-
-    // Send the signal that content has handled and preventDefaulted the touch
-    // events. This flushes the event queue.
-    apzc->ContentReceivedInputBlock(blockId, true);
-
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-    EXPECT_EQ(ParentLayerPoint(), pointOut);
-    EXPECT_EQ(AsyncTransform(), viewTransformOut);
-
-    apzc->AssertStateIsReset();
-  }
-};
-
-TEST_F(APZCPanningTester, Pan) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE);
-}
-
-// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top
-// to bottom and back - from bottom to top.
-// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical
-// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this
-// behavior.
-// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could
-// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the
-// events.
-TEST_F(APZCPanningTester, PanWithTouchActionAuto) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
-                      | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-}
-
-TEST_F(APZCPanningTester, PanWithTouchActionNone) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoPanTest(false, false, 0);
-}
-
-TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
-}
-
-TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
-}
-
-TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoPanWithPreventDefaultTest();
-}
-
-TEST_F(APZCPanningTester, PanWithPreventDefault) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoPanWithPreventDefaultTest();
-}
-
-TEST_F(APZCBasicTester, Fling) {
-  int touchStart = 50;
-  int touchEnd = 10;
-  ParentLayerPoint pointOut;
-  AsyncTransform viewTransformOut;
-
-  // Fling down. Each step scroll further down
-  Pan(apzc, mcc, touchStart, touchEnd);
-  ParentLayerPoint lastPoint;
-  for (int i = 1; i < 50; i+=1) {
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1));
-    EXPECT_GT(pointOut.y, lastPoint.y);
-    lastPoint = pointOut;
-  }
-}
-
-TEST_F(APZCBasicTester, FlingIntoOverscroll) {
-  // Enable overscrolling.
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  // Scroll down by 25 px. Don't fling for simplicity.
-  ApzcPanNoFling(apzc, mcc, 50, 25);
-
-  // Now scroll back up by 20px, this time flinging after.
-  // The fling should cover the remaining 5 px of room to scroll, then
-  // go into overscroll, and finally snap-back to recover from overscroll.
-  Pan(apzc, mcc, 25, 45);
-  const TimeDuration increment = TimeDuration::FromMilliseconds(1);
-  bool reachedOverscroll = false;
-  bool recoveredFromOverscroll = false;
-  while (apzc->AdvanceAnimations(mcc->Time())) {
-    if (!reachedOverscroll && apzc->IsOverscrolled()) {
-      reachedOverscroll = true;
-    }
-    if (reachedOverscroll && !apzc->IsOverscrolled()) {
-      recoveredFromOverscroll = true;
-    }
-    mcc->AdvanceBy(increment);
-  }
-  EXPECT_TRUE(reachedOverscroll);
-  EXPECT_TRUE(recoveredFromOverscroll);
-}
-
-TEST_F(APZCBasicTester, PanningTransformNotifications) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  // Scroll down by 25 px. Ensure we only get one set of
-  // state change notifications.
-  //
-  // Then, scroll back up by 20px, this time flinging after.
-  // The fling should cover the remaining 5 px of room to scroll, then
-  // go into overscroll, and finally snap-back to recover from overscroll.
-  // Again, ensure we only get one set of state change notifications for
-  // this entire procedure.
-
-  MockFunction<void(std::string checkPointName)> check;
-  {
-    InSequence s;
-    EXPECT_CALL(check, Call("Simple pan"));
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
-    EXPECT_CALL(check, Call("Complex pan"));
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
-    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
-    EXPECT_CALL(check, Call("Done"));
-  }
-
-  check.Call("Simple pan");
-  ApzcPanNoFling(apzc, mcc, 50, 25);
-  check.Call("Complex pan");
-  Pan(apzc, mcc, 25, 45);
-  apzc->AdvanceAnimationsUntilEnd();
-  check.Call("Done");
-}
-
-void APZCBasicTester::PanIntoOverscroll()
-{
-  int touchStart = 500;
-  int touchEnd = 10;
-  Pan(apzc, mcc, touchStart, touchEnd);
-  EXPECT_TRUE(apzc->IsOverscrolled());
-}
-
-void APZCBasicTester::TestOverscroll()
-{
-  // Pan sufficiently to hit overscroll behavior
-  PanIntoOverscroll();
-
-  // Check that we recover from overscroll via an animation.
-  ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
-  SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
-}
-
-
-TEST_F(APZCBasicTester, OverScrollPanning) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  TestOverscroll();
-}
-
-// Tests that an overscroll animation doesn't trigger an assertion failure
-// in the case where a sample has a velocity of zero.
-TEST_F(APZCBasicTester, OverScroll_Bug1152051a) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  // Doctor the prefs to make the velocity zero at the end of the first sample.
-
-  // This ensures our incoming velocity to the overscroll animation is
-  // a round(ish) number, 4.9 (that being the distance of the pan before
-  // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
-  // the pan, which is 100 ms).
-  SCOPED_GFX_PREF(APZFlingFriction, float, 0);
-
-  // To ensure the velocity after the first sample is 0, set the spring
-  // stiffness to the incoming velocity (4.9) divided by the overscroll
-  // (400 pixels) times the step duration (1 ms).
-  SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f);
-
-  TestOverscroll();
-}
-
-// Tests that ending an overscroll animation doesn't leave around state that
-// confuses the next overscroll animation.
-TEST_F(APZCBasicTester, OverScroll_Bug1152051b) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f);
-
-  // Pan sufficiently to hit overscroll behavior
-  PanIntoOverscroll();
-
-  // Sample animations once, to give the fling animation started on touch-up
-  // a chance to realize it's overscrolled, and schedule a call to
-  // HandleFlingOverscroll().
-  SampleAnimationOnce();
-
-  // This advances the time and runs the HandleFlingOverscroll task scheduled in
-  // the previous call, which starts an overscroll animation. It then samples
-  // the overscroll animation once, to get it to initialize the first overscroll
-  // sample.
-  SampleAnimationOnce();
-
-  // Do a touch-down to cancel the overscroll animation, and then a touch-up
-  // to schedule a new one since we're still overscrolled. We don't pan because
-  // panning can trigger functions that clear the overscroll animation state
-  // in other ways.
-  TouchDown(apzc, 10, 10, mcc->Time(), nullptr);
-  TouchUp(apzc, 10, 10, mcc->Time());
-
-  // Sample the second overscroll animation to its end.
-  // If the ending of the first overscroll animation fails to clear state
-  // properly, this will assert.
-  ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
-  SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
-}
-
-TEST_F(APZCBasicTester, OverScrollAbort) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  // Pan sufficiently to hit overscroll behavior
-  int touchStart = 500;
-  int touchEnd = 10;
-  Pan(apzc, mcc, touchStart, touchEnd);
-  EXPECT_TRUE(apzc->IsOverscrolled());
-
-  ParentLayerPoint pointOut;
-  AsyncTransform viewTransformOut;
-
-  // This sample call will run to the end of the fling animation
-  // and will schedule the overscroll animation.
-  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000));
-  EXPECT_TRUE(apzc->IsOverscrolled());
-
-  // At this point, we have an active overscroll animation.
-  // Check that cancelling the animation clears the overscroll.
-  apzc->CancelAnimation();
-  EXPECT_FALSE(apzc->IsOverscrolled());
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCBasicTester, OverScrollPanningAbort) {
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  // Pan sufficiently to hit overscroll behaviour. Keep the finger down so
-  // the pan does not end.
-  int touchStart = 500;
-  int touchEnd = 10;
-  Pan(apzc, mcc, touchStart, touchEnd, true); // keep finger down
-  EXPECT_TRUE(apzc->IsOverscrolled());
-
-  // Check that calling CancelAnimation() while the user is still panning
-  // (and thus no fling or snap-back animation has had a chance to start)
-  // clears the overscroll.
-  apzc->CancelAnimation();
-  EXPECT_FALSE(apzc->IsOverscrolled());
-  apzc->AssertStateIsReset();
-}
-
-
-class APZCFlingStopTester : public APZCGestureDetectorTester {
-protected:
-  // Start a fling, and then tap while the fling is ongoing. When
-  // aSlow is false, the tap will happen while the fling is at a
-  // high velocity, and we check that the tap doesn't trigger sending a tap
-  // to content. If aSlow is true, the tap will happen while the fling
-  // is at a slow velocity, and we check that the tap does trigger sending
-  // a tap to content. See bug 1022956.
-  void DoFlingStopTest(bool aSlow) {
-    int touchStart = 50;
-    int touchEnd = 10;
-
-    // Start the fling down.
-    Pan(apzc, mcc, touchStart, touchEnd);
-    // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
-
-    // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
-    // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
-    // friction and threshold values change, but they should be deterministic at least.
-    int timeDelta = aSlow ? 2000 : 10;
-    int tapCallsExpected = aSlow ? 2 : 1;
-
-    // Advance the fling animation by timeDelta milliseconds.
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta));
-
-    // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap
-    // call out of it if and only if the fling is slow.
-    EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected);
-    Tap(apzc, 10, 10, mcc, 0);
-    while (mcc->RunThroughDelayedTasks());
-
-    // Deliver another tap, to make sure that taps are flowing properly once
-    // the fling is aborted.
-    Tap(apzc, 100, 100, mcc, 0);
-    while (mcc->RunThroughDelayedTasks());
-
-    // Verify that we didn't advance any further after the fling was aborted, in either case.
-    ParentLayerPoint finalPointOut;
-    apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut);
-    EXPECT_EQ(pointOut.x, finalPointOut.x);
-    EXPECT_EQ(pointOut.y, finalPointOut.y);
-
-    apzc->AssertStateIsReset();
-  }
-
-  void DoFlingStopWithSlowListener(bool aPreventDefault) {
-    MakeApzcWaitForMainThread();
-
-    int touchStart = 50;
-    int touchEnd = 10;
-    uint64_t blockId = 0;
-
-    // Start the fling down.
-    Pan(apzc, mcc, touchStart, touchEnd, false, nullptr, nullptr, &blockId);
-    apzc->ConfirmTarget(blockId);
-    apzc->ContentReceivedInputBlock(blockId, false);
-
-    // Sample the fling a couple of times to ensure it's going.
-    ParentLayerPoint point, finalPoint;
-    AsyncTransform viewTransform;
-    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
-    apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10));
-    EXPECT_GT(finalPoint.y, point.y);
-
-    // Now we put our finger down to stop the fling
-    TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
-
-    // Re-sample to make sure it hasn't moved
-    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
-    EXPECT_EQ(finalPoint.x, point.x);
-    EXPECT_EQ(finalPoint.y, point.y);
-
-    // respond to the touchdown that stopped the fling.
-    // even if we do a prevent-default on it, the animation should remain stopped.
-    apzc->ContentReceivedInputBlock(blockId, aPreventDefault);
-
-    // Verify the page hasn't moved
-    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70));
-    EXPECT_EQ(finalPoint.x, point.x);
-    EXPECT_EQ(finalPoint.y, point.y);
-
-    // clean up
-    TouchUp(apzc, 10, 10, mcc->Time());
-
-    apzc->AssertStateIsReset();
-  }
-};
-
-TEST_F(APZCFlingStopTester, FlingStop) {
-  DoFlingStopTest(false);
-}
-
-TEST_F(APZCFlingStopTester, FlingStopTap) {
-  DoFlingStopTest(true);
-}
-
-TEST_F(APZCFlingStopTester, FlingStopSlowListener) {
-  DoFlingStopWithSlowListener(false);
-}
-
-TEST_F(APZCFlingStopTester, FlingStopPreventDefault) {
-  DoFlingStopWithSlowListener(true);
-}
-
-TEST_F(APZCGestureDetectorTester, ShortPress) {
-  MakeApzcUnzoomable();
-
-  MockFunction<void(std::string checkPointName)> check;
-  {
-    InSequence s;
-    // This verifies that the single tap notification is sent after the
-    // touchup is fully processed. The ordering here is important.
-    EXPECT_CALL(check, Call("pre-tap"));
-    EXPECT_CALL(check, Call("post-tap"));
-    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-  }
-
-  check.Call("pre-tap");
-  TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
-  check.Call("post-tap");
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, MediumPress) {
-  MakeApzcUnzoomable();
-
-  MockFunction<void(std::string checkPointName)> check;
-  {
-    InSequence s;
-    // This verifies that the single tap notification is sent after the
-    // touchup is fully processed. The ordering here is important.
-    EXPECT_CALL(check, Call("pre-tap"));
-    EXPECT_CALL(check, Call("post-tap"));
-    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-  }
-
-  check.Call("pre-tap");
-  TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400));
-  check.Call("post-tap");
-
-  apzc->AssertStateIsReset();
-}
-
-class APZCLongPressTester : public APZCGestureDetectorTester {
-protected:
-  void DoLongPressTest(uint32_t aBehavior) {
-    MakeApzcUnzoomable();
-
-    uint64_t blockId = 0;
-
-    nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
-
-    if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
-      // SetAllowedTouchBehavior() must be called after sending touch-start.
-      nsTArray<uint32_t> allowedTouchBehaviors;
-      allowedTouchBehaviors.AppendElement(aBehavior);
-      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
-    }
-    // Have content "respond" to the touchstart
-    apzc->ContentReceivedInputBlock(blockId, false);
-
-    MockFunction<void(std::string checkPointName)> check;
-
-    {
-      InSequence s;
-
-      EXPECT_CALL(check, Call("preHandleLongTap"));
-      blockId++;
-      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1);
-      EXPECT_CALL(check, Call("postHandleLongTap"));
-
-      EXPECT_CALL(check, Call("preHandleSingleTap"));
-      EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-      EXPECT_CALL(check, Call("postHandleSingleTap"));
-    }
-
-    // Manually invoke the longpress while the touch is currently down.
-    check.Call("preHandleLongTap");
-    mcc->RunThroughDelayedTasks();
-    check.Call("postHandleLongTap");
-
-    // Dispatching the longpress event starts a new touch block, which
-    // needs a new content response and also has a pending timeout task
-    // in the queue. Deal with those here. We do the content response first
-    // with preventDefault=false, and then we run the timeout task which
-    // "loses the race" and does nothing.
-    apzc->ContentReceivedInputBlock(blockId, false);
-    mcc->AdvanceByMillis(1000);
-
-    // Finally, simulate lifting the finger. Since the long-press wasn't
-    // prevent-defaulted, we should get a long-tap-up event.
-    check.Call("preHandleSingleTap");
-    status = TouchUp(apzc, 10, 10, mcc->Time());
-    mcc->RunThroughDelayedTasks();
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
-    check.Call("postHandleSingleTap");
-
-    apzc->AssertStateIsReset();
-  }
-
-  void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
-    MakeApzcUnzoomable();
-
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
-
-    int touchX = 10,
-        touchStartY = 10,
-        touchEndY = 50;
-
-    uint64_t blockId = 0;
-    nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId);
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
-
-    if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
-      // SetAllowedTouchBehavior() must be called after sending touch-start.
-      nsTArray<uint32_t> allowedTouchBehaviors;
-      allowedTouchBehaviors.AppendElement(aBehavior);
-      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
-    }
-    // Have content "respond" to the touchstart
-    apzc->ContentReceivedInputBlock(blockId, false);
-
-    MockFunction<void(std::string checkPointName)> check;
-
-    {
-      InSequence s;
-
-      EXPECT_CALL(check, Call("preHandleLongTap"));
-      blockId++;
-      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1);
-      EXPECT_CALL(check, Call("postHandleLongTap"));
-    }
-
-    // Manually invoke the longpress while the touch is currently down.
-    check.Call("preHandleLongTap");
-    mcc->RunThroughDelayedTasks();
-    check.Call("postHandleLongTap");
-
-    // There should be a TimeoutContentResponse task in the queue still,
-    // waiting for the response from the longtap event dispatched above.
-    // Send the signal that content has handled the long-tap, and then run
-    // the timeout task (it will be a no-op because the content "wins" the
-    // race. This takes the place of the "contextmenu" event.
-    apzc->ContentReceivedInputBlock(blockId, true);
-    mcc->AdvanceByMillis(1000);
-
-    MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time());
-    mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
-    status = apzc->ReceiveInputEvent(mti, nullptr);
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
-
-    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
-    status = TouchUp(apzc, touchX, touchEndY, mcc->Time());
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
-
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-
-    EXPECT_EQ(ParentLayerPoint(), pointOut);
-    EXPECT_EQ(AsyncTransform(), viewTransformOut);
-
-    apzc->AssertStateIsReset();
-  }
-};
-
-TEST_F(APZCLongPressTester, LongPress) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE);
-}
-
-TEST_F(APZCLongPressTester, LongPressWithTouchAction) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
-                  | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
-                  | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-}
-
-TEST_F(APZCLongPressTester, LongPressPreventDefault) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-  DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE);
-}
-
-TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
-  DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
-                                | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
-                                | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
-}
-
-template<class InputReceiver> static void
-DoubleTap(const RefPtr<InputReceiver>& aTarget, int aX, int aY, MockContentControllerDelayed* aMcc,
-          nsEventStatus (*aOutEventStatuses)[4] = nullptr,
-          uint64_t (*aOutInputBlockIds)[2] = nullptr)
-{
-  uint64_t blockId;
-  nsEventStatus status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[0] = status;
-  }
-  if (aOutInputBlockIds) {
-    (*aOutInputBlockIds)[0] = blockId;
-  }
-  aMcc->AdvanceByMillis(10);
-
-  // If touch-action is enabled then simulate the allowed touch behaviour
-  // notification that the main thread is supposed to deliver.
-  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
-    SetDefaultAllowedTouchBehavior(aTarget, blockId);
-  }
-
-  status = TouchUp(aTarget, aX, aY, aMcc->Time());
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[1] = status;
-  }
-  aMcc->AdvanceByMillis(10);
-  status = TouchDown(aTarget, aX, aY, aMcc->Time(), &blockId);
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[2] = status;
-  }
-  if (aOutInputBlockIds) {
-    (*aOutInputBlockIds)[1] = blockId;
-  }
-  aMcc->AdvanceByMillis(10);
-
-  if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
-    SetDefaultAllowedTouchBehavior(aTarget, blockId);
-  }
-
-  status = TouchUp(aTarget, aX, aY, aMcc->Time());
-  if (aOutEventStatuses) {
-    (*aOutEventStatuses)[3] = status;
-  }
-}
-
-template<class InputReceiver> static void
-DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aX, int aY,
-    MockContentControllerDelayed* aMcc, uint64_t (*aOutInputBlockIds)[2] = nullptr)
-{
-  nsEventStatus statuses[4];
-  DoubleTap(aTarget, aX, aY, aMcc, &statuses, aOutInputBlockIds);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
-}
-
-TEST_F(APZCGestureDetectorTester, DoubleTap) {
-  MakeApzcWaitForMainThread();
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
-  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-
-  uint64_t blockIds[2];
-  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
-
-  // responses to the two touchstarts
-  apzc->ContentReceivedInputBlock(blockIds[0], false);
-  apzc->ContentReceivedInputBlock(blockIds[1], false);
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
-  MakeApzcWaitForMainThread();
-  MakeApzcUnzoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2);
-  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
-
-  uint64_t blockIds[2];
-  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
-
-  // responses to the two touchstarts
-  apzc->ContentReceivedInputBlock(blockIds[0], false);
-  apzc->ContentReceivedInputBlock(blockIds[1], false);
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
-  MakeApzcWaitForMainThread();
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
-
-  uint64_t blockIds[2];
-  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
-
-  // responses to the two touchstarts
-  apzc->ContentReceivedInputBlock(blockIds[0], true);
-  apzc->ContentReceivedInputBlock(blockIds[1], false);
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
-  MakeApzcWaitForMainThread();
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
-  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
-
-  uint64_t blockIds[2];
-  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
-
-  // responses to the two touchstarts
-  apzc->ContentReceivedInputBlock(blockIds[0], true);
-  apzc->ContentReceivedInputBlock(blockIds[1], true);
-
-  apzc->AssertStateIsReset();
-}
-
-// Test for bug 947892
-// We test whether we dispatch tap event when the tap is followed by pinch.
-TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) {
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-
-  Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
-
-  int inputId = 0;
-  MultiTouchInput mti;
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  apzc->AssertStateIsReset();
-}
-
-TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) {
-  MakeApzcZoomable();
-
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
-
-  Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
-
-  int inputId = 0;
-  MultiTouchInput mti;
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
-  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
-  apzc->ReceiveInputEvent(mti, nullptr);
-
-  apzc->AssertStateIsReset();
-}
-
-class APZCTreeManagerTester : public ::testing::Test {
-protected:
-  virtual void SetUp() {
-    gfxPrefs::GetSingleton();
-    APZThreadUtils::SetThreadAssertionsEnabled(false);
-    APZThreadUtils::SetControllerThread(MessageLoop::current());
-
-    mcc = new NiceMock<MockContentControllerDelayed>();
-    manager = new TestAPZCTreeManager(mcc);
-  }
-
-  virtual void TearDown() {
-    while (mcc->RunThroughDelayedTasks());
-    manager->ClearTree();
-  }
-
-  /**
-   * Sample animations once for all APZCs, 1 ms later than the last sample.
-   */
-  void SampleAnimationsOnce() {
-    const TimeDuration increment = TimeDuration::FromMilliseconds(1);
-    ParentLayerPoint pointOut;
-    AsyncTransform viewTransformOut;
-    mcc->AdvanceBy(increment);
-
-    for (const RefPtr<Layer>& layer : layers) {
-      if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) {
-        apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
-      }
-    }
-  }
-
-  RefPtr<MockContentControllerDelayed> mcc;
-
-  nsTArray<RefPtr<Layer> > layers;
-  RefPtr<LayerManager> lm;
-  RefPtr<Layer> root;
-
-  RefPtr<TestAPZCTreeManager> manager;
-
-protected:
-  static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
-                                        CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
-    FrameMetrics metrics;
-    metrics.SetScrollId(aScrollId);
-    // By convention in this test file, START_SCROLL_ID is the root, so mark it as such.
-    if (aScrollId == FrameMetrics::START_SCROLL_ID) {
-      metrics.SetIsLayersIdRoot(true);
-    }
-    IntRect layerBound = aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds();
-    metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y,
-                                                 layerBound.width, layerBound.height));
-    metrics.SetScrollableRect(aScrollableRect);
-    metrics.SetScrollOffset(CSSPoint(0, 0));
-    metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
-    metrics.SetAllowVerticalScrollWithWheel(true);
-    aLayer->SetFrameMetrics(metrics);
-    aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound)));
-    if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
-      // The purpose of this is to roughly mimic what layout would do in the
-      // case of a scrollable frame with the event regions and clip. This lets
-      // us exercise the hit-testing code in APZCTreeManager
-      EventRegions er = aLayer->GetEventRegions();
-      IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
-      er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size()));
-      aLayer->SetEventRegions(er);
-    }
-  }
-
-  void SetScrollHandoff(Layer* aChild, Layer* aParent) {
-    FrameMetrics metrics = aChild->GetFrameMetrics(0);
-    metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
-    aChild->SetFrameMetrics(metrics);
-  }
-
-  static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) {
-    EXPECT_EQ(1u, aLayer->GetFrameMetricsCount());
-    return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0);
-  }
-
-  void CreateSimpleScrollingLayer() {
-    const char* layerTreeSyntax = "t";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,200,200)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
-  }
-
-  void CreateSimpleDTCScrollingLayer() {
-    const char* layerTreeSyntax = "t";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,200,200)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
-
-    EventRegions regions;
-    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200));
-    regions.mDispatchToContentHitRegion = regions.mHitRegion;
-    layers[0]->SetEventRegions(regions);
-  }
-
-  void CreateSimpleMultiLayerTree() {
-    const char* layerTreeSyntax = "c(tt)";
-    // LayerID                     0 12
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,100,100)),
-      nsIntRegion(IntRect(0,0,100,50)),
-      nsIntRegion(IntRect(0,50,100,50)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-  }
-
-  void CreatePotentiallyLeakingTree() {
-    const char* layerTreeSyntax = "c(c(c(t))c(c(t)))";
-    // LayerID                     0 1 2 3  4 5 6
-    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
-    SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2);
-    SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3);
-  }
-
-  void CreateBug1194876Tree() {
-    const char* layerTreeSyntax = "c(t)";
-    // LayerID                     0 1
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,100,100)),
-      nsIntRegion(IntRect(0,0,100,100)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
-
-    // Make layers[1] the root content
-    FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0);
-    childMetrics.SetIsRootContent(true);
-    layers[1]->SetFrameMetrics(childMetrics);
-
-    // Both layers are fully dispatch-to-content
-    EventRegions regions;
-    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
-    regions.mDispatchToContentHitRegion = regions.mHitRegion;
-    layers[0]->SetEventRegions(regions);
-    layers[1]->SetEventRegions(regions);
-  }
-};
-
-class APZHitTestingTester : public APZCTreeManagerTester {
-protected:
-  ScreenToParentLayerMatrix4x4 transformToApzc;
-  ParentLayerToScreenMatrix4x4 transformToGecko;
-
-  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) {
-    RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr);
-    if (hit) {
-      transformToApzc = manager->GetScreenToApzcTransform(hit.get());
-      transformToGecko = manager->GetApzcToGeckoTransform(hit.get());
-    }
-    return hit.forget();
-  }
-
-protected:
-  void CreateHitTesting1LayerTree() {
-    const char* layerTreeSyntax = "c(tttt)";
-    // LayerID                     0 1234
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,100,100)),
-      nsIntRegion(IntRect(0,0,100,100)),
-      nsIntRegion(IntRect(10,10,20,20)),
-      nsIntRegion(IntRect(10,10,20,20)),
-      nsIntRegion(IntRect(5,5,20,20)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-  }
-
-  void CreateHitTesting2LayerTree() {
-    const char* layerTreeSyntax = "c(tc(t))";
-    // LayerID                     0 12 3
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,100,100)),
-      nsIntRegion(IntRect(10,10,40,40)),
-      nsIntRegion(IntRect(10,60,40,40)),
-      nsIntRegion(IntRect(10,60,40,40)),
-    };
-    Matrix4x4 transforms[] = {
-      Matrix4x4(),
-      Matrix4x4(),
-      Matrix4x4::Scaling(2, 1, 1),
-      Matrix4x4(),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
-
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
-    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80));
-  }
-
-  void CreateComplexMultiLayerTree() {
-    const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))";
-    // LayerID                     0 12 3 45 6 7 89
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,300,400)),      // root(0)
-      nsIntRegion(IntRect(0,0,100,100)),      // thebes(1) in top-left
-      nsIntRegion(IntRect(50,50,200,300)),    // container(2) centered in root(0)
-      nsIntRegion(IntRect(50,50,200,300)),    // thebes(3) fully occupying parent container(2)
-      nsIntRegion(IntRect(0,200,100,100)),    // thebes(4) in bottom-left
-      nsIntRegion(IntRect(200,0,100,400)),    // container(5) along the right 100px of root(0)
-      nsIntRegion(IntRect(200,0,100,200)),    // container(6) taking up the top half of parent container(5)
-      nsIntRegion(IntRect(200,0,100,200)),    // thebes(7) fully occupying parent container(6)
-      nsIntRegion(IntRect(200,200,100,100)),  // thebes(8) in bottom-right (below (6))
-      nsIntRegion(IntRect(200,300,100,100)),  // thebes(9) in bottom-right (below (8))
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2);
-    SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3);
-  }
-
-  void CreateBug1148350LayerTree() {
-    const char* layerTreeSyntax = "c(t)";
-    // LayerID                     0 1
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0,0,200,200)),
-      nsIntRegion(IntRect(0,0,200,200)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
-  }
-};
-
-// A simple hit testing test that doesn't involve any transforms on layers.
-TEST_F(APZHitTestingTester, HitTesting1) {
-  CreateHitTesting1LayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-
-  // No APZC attached so hit testing will return no APZC at (20,20)
-  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
-  TestAsyncPanZoomController* nullAPZC = nullptr;
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
-  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
-
-  uint32_t paintSequenceNumber = 0;
-
-  // Now we have a root APZC that will match the page
-  SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
-  hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(ApzcOf(root), hit.get());
-  // expect hit point at LayerIntPoint(15, 15)
-  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
-  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
-
-  // Now we have a sub APZC with a better fit
-  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
-  EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
-  hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(ApzcOf(layers[3]), hit.get());
-  // expect hit point at LayerIntPoint(25, 25)
-  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
-  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
-
-  // At this point, layers[4] obscures layers[3] at the point (15, 15) so
-  // hitting there should hit the root APZC
-  hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(ApzcOf(root), hit.get());
-
-  // Now test hit testing when we have two scrollable layers
-  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
-  hit = GetTargetAPZC(ScreenPoint(15, 15));
-  EXPECT_EQ(ApzcOf(layers[4]), hit.get());
-  // expect hit point at LayerIntPoint(15, 15)
-  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
-  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
-
-  // Hit test ouside the reach of layer[3,4] but inside root
-  hit = GetTargetAPZC(ScreenPoint(90, 90));
-  EXPECT_EQ(ApzcOf(root), hit.get());
-  // expect hit point at LayerIntPoint(90, 90)
-  EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90));
-  EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90));
-
-  // Hit test ouside the reach of any layer
-  hit = GetTargetAPZC(ScreenPoint(1000, 10));
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
-  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
-  hit = GetTargetAPZC(ScreenPoint(-1000, 10));
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
-  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
-}
-
-// A more involved hit testing test that involves css and async transforms.
-TEST_F(APZHitTestingTester, HitTesting2) {
-  CreateHitTesting2LayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  // At this point, the following holds (all coordinates in screen pixels):
-  // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
-  // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
-  // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
-  // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
-
-  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
-  TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
-  TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
-
-  // Hit an area that's clearly on the root layer but not any of the child layers.
-  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
-  EXPECT_EQ(apzcroot, hit.get());
-  EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25));
-  EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25));
-
-  // Hit an area on the root that would be on layers[3] if layers[2]
-  // weren't transformed.
-  // Note that if layers[2] were scrollable, then this would hit layers[2]
-  // because its composition bounds would be at (10,60)-(50,100) (and the
-  // scale-only transform that we set on layers[2] would be invalid because
-  // it would place the layer into overscroll, as its composition bounds
-  // start at x=10 but its content at x=20).
-  hit = GetTargetAPZC(ScreenPoint(15, 75));
-  EXPECT_EQ(apzcroot, hit.get());
-  EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75));
-  EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75));
-
-  // Hit an area on layers[1].
-  hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(apzc1, hit.get());
-  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
-  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
-
-  // Hit an area on layers[3].
-  hit = GetTargetAPZC(ScreenPoint(25, 75));
-  EXPECT_EQ(apzc3, hit.get());
-  // transformToApzc should unapply layers[2]'s transform
-  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75));
-  // and transformToGecko should reapply it
-  EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75));
-
-  // Hit an area on layers[3] that would be on the root if layers[2]
-  // weren't transformed.
-  hit = GetTargetAPZC(ScreenPoint(75, 75));
-  EXPECT_EQ(apzc3, hit.get());
-  // transformToApzc should unapply layers[2]'s transform
-  EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75));
-  // and transformToGecko should reapply it
-  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75));
-
-  // Pan the root layer upward by 50 pixels.
-  // This causes layers[1] to scroll out of view, and an async transform
-  // of -50 to be set on the root layer.
-  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
-
-  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
-  // Since this paint request is in the queue to Gecko, transformToGecko will
-  // take it into account.
-  ApzcPanNoFling(apzcroot, mcc, 100, 50);
-
-  // Hit where layers[3] used to be. It should now hit the root.
-  hit = GetTargetAPZC(ScreenPoint(75, 75));
-  EXPECT_EQ(apzcroot, hit.get());
-  // transformToApzc doesn't unapply the root's own async transform
-  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
-  // and transformToGecko unapplies it and then reapplies it, because by the
-  // time the event being transformed reaches Gecko the new paint request will
-  // have been handled.
-  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
-
-  // Hit where layers[1] used to be and where layers[3] should now be.
-  hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(apzc3, hit.get());
-  // transformToApzc unapplies both layers[2]'s css transform and the root's
-  // async transform
-  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25));
-  // transformToGecko reapplies both the css transform and the async transform
-  // because we have already issued a paint request with it.
-  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75));
-
-  // This second pan will move the APZC by another 50 pixels.
-  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
-  ApzcPanNoFling(apzcroot, mcc, 100, 50);
-
-  // Hit where layers[3] used to be. It should now hit the root.
-  hit = GetTargetAPZC(ScreenPoint(75, 75));
-  EXPECT_EQ(apzcroot, hit.get());
-  // transformToApzc doesn't unapply the root's own async transform
-  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
-  // transformToGecko unapplies the full async transform of -100 pixels
-  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
-
-  // Hit where layers[1] used to be. It should now hit the root.
-  hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(apzcroot, hit.get());
-  // transformToApzc doesn't unapply the root's own async transform
-  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
-  // transformToGecko unapplies the full async transform of -100 pixels
-  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
-}
-
-TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) {
-  CreateSimpleMultiLayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-
-  // both layers have the same scrollId
-  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
-  SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  TestAsyncPanZoomController* nullAPZC = nullptr;
-  // so they should have the same APZC
-  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
-  EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
-  EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
-  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
-
-  // Change the scrollId of layers[1], and verify the APZC changes
-  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2]));
-
-  // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
-  // APZC for both again
-  SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
-}
-
-TEST_F(APZCTreeManagerTester, Bug1068268) {
-  CreatePotentiallyLeakingTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
-  RefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild();
-  RefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild();
-
-  EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc());
-  EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc());
-  EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
-
-  EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc());
-  EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild());
-  EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc());
-  EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent());
-  EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
-}
-
-TEST_F(APZCTreeManagerTester, Bug1194876) {
-  CreateBug1194876Tree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  uint64_t blockId;
-  nsTArray<ScrollableLayerGuid> targets;
-
-  // First touch goes down, APZCTM will hit layers[1] because it is on top of
-  // layers[0], but we tell it the real target APZC is layers[0].
-  MultiTouchInput mti;
-  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0));
-  manager->ReceiveInputEvent(mti, nullptr, &blockId);
-  manager->ContentReceivedInputBlock(blockId, false);
-  targets.AppendElement(ApzcOf(layers[0])->GetGuid());
-  manager->SetTargetAPZC(blockId, targets);
-
-  // Around here, the above touch will get processed by ApzcOf(layers[0])
-
-  // Second touch goes down (first touch remains down), APZCTM will again hit
-  // layers[1]. Again we tell it both touches landed on layers[0], but because
-  // layers[1] is the RCD layer, it will end up being the multitouch target.
-  mti.mTouches.AppendElement(SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0));
-  manager->ReceiveInputEvent(mti, nullptr, &blockId);
-  manager->ContentReceivedInputBlock(blockId, false);
-  targets.AppendElement(ApzcOf(layers[0])->GetGuid());
-  manager->SetTargetAPZC(blockId, targets);
-
-  // Around here, the above multi-touch will get processed by ApzcOf(layers[1]).
-  // We want to ensure that ApzcOf(layers[0]) has had its state cleared, because
-  // otherwise it will do things like dispatch spurious long-tap events.
-
-  EXPECT_CALL(*mcc, HandleLongTap(_, _, _, _)).Times(0);
-}
-
-TEST_F(APZCTreeManagerTester, Bug1198900) {
-  // This is just a test that cancels a wheel event to make sure it doesn't
-  // crash.
-  CreateSimpleDTCScrollingLayer();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  ScreenPoint origin(100, 50);
-  ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
-    ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-    origin, 0, 10);
-  uint64_t blockId;
-  manager->ReceiveInputEvent(swi, nullptr, &blockId);
-  manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true);
-}
-
-TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
-  CreateComplexMultiLayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  /* The layer tree looks like this:
-
-                0
-        |----|--+--|----|
-        1    2     4    5
-             |         /|\
-             3        6 8 9
-                      |
-                      7
-
-     Layers 1,2 have the same APZC
-     Layers 4,6,8 have the same APZC
-     Layer 7 has an APZC
-     Layer 9 has an APZC
-  */
-
-  TestAsyncPanZoomController* nullAPZC = nullptr;
-  // Ensure all the scrollable layers have an APZC
-  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
-  EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
-  EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
-  EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
-  EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
-  EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
-  EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
-  EXPECT_NE(nullAPZC, ApzcOf(layers[7]));
-  EXPECT_NE(nullAPZC, ApzcOf(layers[8]));
-  EXPECT_NE(nullAPZC, ApzcOf(layers[9]));
-  // Ensure those that scroll together have the same APZCs
-  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
-  EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6]));
-  EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
-  // Ensure those that don't scroll together have different APZCs
-  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
-  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
-  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
-  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
-  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
-  EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
-  // Ensure the APZC parent chains are set up correctly
-  TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
-  TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
-  TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
-  TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
-  EXPECT_EQ(nullptr, layers1_2->GetParent());
-  EXPECT_EQ(nullptr, layers4_6_8->GetParent());
-  EXPECT_EQ(layers4_6_8, layer7->GetParent());
-  EXPECT_EQ(nullptr, layer9->GetParent());
-  // Ensure the hit-testing tree looks like the layer tree
-  RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
-  RefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
-  RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
-  RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
-  RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
-  RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
-  RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
-  RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
-  RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
-  RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
-  EXPECT_EQ(nullptr, node1->GetPrevSibling());
-  EXPECT_EQ(nullptr, node3->GetPrevSibling());
-  EXPECT_EQ(nullptr, node6->GetPrevSibling());
-  EXPECT_EQ(nullptr, node7->GetPrevSibling());
-  EXPECT_EQ(nullptr, node1->GetLastChild());
-  EXPECT_EQ(nullptr, node3->GetLastChild());
-  EXPECT_EQ(nullptr, node4->GetLastChild());
-  EXPECT_EQ(nullptr, node7->GetLastChild());
-  EXPECT_EQ(nullptr, node8->GetLastChild());
-  EXPECT_EQ(nullptr, node9->GetLastChild());
-
-  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
-  EXPECT_EQ(ApzcOf(layers[1]), hit.get());
-  hit = GetTargetAPZC(ScreenPoint(275, 375));
-  EXPECT_EQ(ApzcOf(layers[9]), hit.get());
-  hit = GetTargetAPZC(ScreenPoint(250, 100));
-  EXPECT_EQ(ApzcOf(layers[7]), hit.get());
-}
-
-TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) {
-  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
-
-  // The main purpose of this test is to verify that touch-start events (or anything
-  // that starts a new input block) don't ever get untransformed. This should always
-  // hold because the APZ code should flush repaints when we start a new input block
-  // and the transform to gecko space should be empty.
-
-  CreateSimpleScrollingLayer();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
-
-  // At this point, the following holds (all coordinates in screen pixels):
-  // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200)
-
-  MockFunction<void(std::string checkPointName)> check;
-
-  {
-    InSequence s;
-
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
-    EXPECT_CALL(check, Call("post-first-touch-start"));
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
-    EXPECT_CALL(check, Call("post-second-fling"));
-    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
-    EXPECT_CALL(check, Call("post-second-touch-start"));
-  }
-
-  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
-  ApzcPanNoFling(apzcroot, mcc, 100, 50);
-
-  // Verify that a touch start doesn't get untransformed
-  ScreenIntPoint touchPoint(50, 50);
-  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
-  mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0));
-
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
-  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
-  check.Call("post-first-touch-start");
-
-  // Send a touchend to clear state
-  mti.mType = MultiTouchInput::MULTITOUCH_END;
-  manager->ReceiveInputEvent(mti, nullptr, nullptr);
-
-  mcc->AdvanceByMillis(1000);
-
-  // Now do two pans. The first of these will dispatch a repaint request, as above.
-  // The second will get stuck in the paint throttler because the first one doesn't
-  // get marked as "completed", so this will result in a non-empty LD transform.
-  // (Note that any outstanding repaint requests from the first half of this test
-  // don't impact this half because we advance the time by 1 second, which will trigger
-  // the max-wait-exceeded codepath in the paint throttler).
-  ApzcPanNoFling(apzcroot, mcc, 100, 50);
-  check.Call("post-second-fling");
-  ApzcPanNoFling(apzcroot, mcc, 100, 50);
-
-  // Ensure that a touch start again doesn't get untransformed by flushing
-  // a repaint
-  mti.mType = MultiTouchInput::MULTITOUCH_START;
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
-  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
-  check.Call("post-second-touch-start");
-
-  mti.mType = MultiTouchInput::MULTITOUCH_END;
-  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
-  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
-}
-
-TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) {
-  // The purpose of this test is to ensure that wheel events trigger a repaint
-  // flush as per bug 1166871, and that the wheel event untransform is a no-op.
-
-  CreateSimpleScrollingLayer();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
-
-  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3));
-  ScreenPoint origin(100, 50);
-  for (int i = 0; i < 3; i++) {
-    ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
-      ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
-      origin, 0, 10);
-    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
-    EXPECT_EQ(origin, swi.mOrigin);
-
-    AsyncTransform viewTransform;
-    ParentLayerPoint point;
-    apzcroot->SampleContentTransformForFrame(&viewTransform, point);
-    EXPECT_EQ(0, point.x);
-    EXPECT_EQ((i + 1) * 10, point.y);
-    EXPECT_EQ(0, viewTransform.mTranslation.x);
-    EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y);
-
-    mcc->AdvanceByMillis(5);
-  }
-}
-
-TEST_F(APZHitTestingTester, Bug1148350) {
-  CreateBug1148350LayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  MockFunction<void(std::string checkPointName)> check;
-  {
-    InSequence s;
-    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped without transform"));
-    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped with interleaved transform"));
-  }
-
-  Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100));
-  mcc->RunThroughDelayedTasks();
-  check.Call("Tapped without transform");
-
-  uint64_t blockId;
-  TouchDown(manager, 100, 100, mcc->Time(), &blockId);
-  if (gfxPrefs::TouchActionEnabled()) {
-    SetDefaultAllowedTouchBehavior(manager, blockId);
-  }
-  mcc->AdvanceByMillis(100);
-
-  layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150)));
-  layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0));
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  TouchUp(manager, 100, 100, mcc->Time());
-  mcc->RunThroughDelayedTasks();
-  check.Call("Tapped with interleaved transform");
-}
-
-class APZOverscrollHandoffTester : public APZCTreeManagerTester {
-protected:
-  UniquePtr<ScopedLayerTreeRegistration> registration;
-  TestAsyncPanZoomController* rootApzc;
-
-  void CreateOverscrollHandoffLayerTree1() {
-    const char* layerTreeSyntax = "c(t)";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 50, 100, 50))
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
-    SetScrollHandoff(layers[1], root);
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-  }
-
-  void CreateOverscrollHandoffLayerTree2() {
-    const char* layerTreeSyntax = "c(c(t))";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 50, 100, 50))
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
-    SetScrollHandoff(layers[1], root);
-    SetScrollHandoff(layers[2], layers[1]);
-    // No ScopedLayerTreeRegistration as that just needs to be done once per test
-    // and this is the second layer tree for a particular test.
-    MOZ_ASSERT(registration);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-  }
-
-  void CreateOverscrollHandoffLayerTree3() {
-    const char* layerTreeSyntax = "c(c(t)c(t))";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),  // root
-      nsIntRegion(IntRect(0, 0, 100, 50)),   // scrolling parent 1
-      nsIntRegion(IntRect(0, 0, 100, 50)),   // scrolling child 1
-      nsIntRegion(IntRect(0, 50, 100, 50)),  // scrolling parent 2
-      nsIntRegion(IntRect(0, 50, 100, 50))   // scrolling child 2
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100));
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
-    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100));
-    SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100));
-    SetScrollHandoff(layers[2], layers[1]);
-    SetScrollHandoff(layers[4], layers[3]);
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  }
-
-  void CreateScrollgrabLayerTree(bool makeParentScrollable = true) {
-    const char* layerTreeSyntax = "c(t)";
-    nsIntRegion layerVisibleRegion[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),  // scroll-grabbing parent
-      nsIntRegion(IntRect(0, 20, 100, 80))   // child
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
-    float parentHeight = makeParentScrollable ? 120 : 100;
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight));
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
-    SetScrollHandoff(layers[1], root);
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-    rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
-  }
-
-  void TestFlingAcceleration() {
-    // Jack up the fling acceleration multiplier so we can easily determine
-    // whether acceleration occured.
-    const float kAcceleration = 100.0f;
-    SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration);
-
-    RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
-
-    // Pan once, enough to fully scroll the scrollgrab parent and then scroll
-    // and fling the child.
-    Pan(manager, mcc, 70, 40);
-
-    // Give the fling animation a chance to start.
-    SampleAnimationsOnce();
-
-    float childVelocityAfterFling1 = childApzc->GetVelocityVector().y;
-
-    // Pan again.
-    Pan(manager, mcc, 70, 40);
-
-    // Give the fling animation a chance to start.
-    // This time it should be accelerated.
-    SampleAnimationsOnce();
-
-    float childVelocityAfterFling2 = childApzc->GetVelocityVector().y;
-
-    // We should have accelerated once.
-    // The division by 2 is to account for friction.
-    EXPECT_GT(childVelocityAfterFling2,
-              childVelocityAfterFling1 * kAcceleration / 2);
-
-    // We should not have accelerated twice.
-    // The division by 4 is to account for friction.
-    EXPECT_LE(childVelocityAfterFling2,
-              childVelocityAfterFling1 * kAcceleration * kAcceleration / 4);
-  }
-};
-
-// Here we test that if the processing of a touch block is deferred while we
-// wait for content to send a prevent-default message, overscroll is still
-// handed off correctly when the block is processed.
-TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
-  // Set up the APZC tree.
-  CreateOverscrollHandoffLayerTree1();
-
-  TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
-
-  // Enable touch-listeners so that we can separate the queueing of input
-  // events from them being processed.
-  childApzc->SetWaitForMainThread();
-
-  // Queue input events for a pan.
-  uint64_t blockId = 0;
-  ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
-
-  // Allow the pan to be processed.
-  childApzc->ContentReceivedInputBlock(blockId, false);
-  childApzc->ConfirmTarget(blockId);
-
-  // Make sure overscroll was handed off correctly.
-  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
-}
-
-// Here we test that if the layer structure changes in between two input
-// blocks being queued, and the first block is only processed after the second
-// one has been queued, overscroll handoff for the first block follows
-// the original layer structure while overscroll handoff for the second block
-// follows the new layer structure.
-TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
-  // Set up an initial APZC tree.
-  CreateOverscrollHandoffLayerTree1();
-
-  TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
-
-  // Enable touch-listeners so that we can separate the queueing of input
-  // events from them being processed.
-  childApzc->SetWaitForMainThread();
-
-  // Queue input events for a pan.
-  uint64_t blockId = 0;
-  ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
-
-  // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
-  // between the child and the root.
-  CreateOverscrollHandoffLayerTree2();
-  RefPtr<Layer> middle = layers[1];
-  childApzc->SetWaitForMainThread();
-  TestAsyncPanZoomController* middleApzc = ApzcOf(middle);
-
-  // Queue input events for another pan.
-  uint64_t secondBlockId = 0;
-  ApzcPanNoFling(childApzc, mcc, 30, 90, &secondBlockId);
-
-  // Allow the first pan to be processed.
-  childApzc->ContentReceivedInputBlock(blockId, false);
-  childApzc->ConfirmTarget(blockId);
-
-  // Make sure things have scrolled according to the handoff chain in
-  // place at the time the touch-start of the first pan was queued.
-  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y);
-
-  // Allow the second pan to be processed.
-  childApzc->ContentReceivedInputBlock(secondBlockId, false);
-  childApzc->ConfirmTarget(secondBlockId);
-
-  // Make sure things have scrolled according to the handoff chain in
-  // place at the time the touch-start of the second pan was queued.
-  EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y);
-}
-
-// Test that putting a second finger down on an APZC while a down-chain APZC
-// is overscrolled doesn't result in being stuck in overscroll.
-TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) {
-  // Enable overscrolling.
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  CreateOverscrollHandoffLayerTree1();
-
-  TestAsyncPanZoomController* child = ApzcOf(layers[1]);
-
-  // Pan, causing the parent APZC to overscroll.
-  Pan(manager, mcc, 10, 40, true /* keep finger down */);
-  EXPECT_FALSE(child->IsOverscrolled());
-  EXPECT_TRUE(rootApzc->IsOverscrolled());
-
-  // Put a second finger down.
-  MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
-  secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
-  secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
-  manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
-
-  // Release the fingers.
-  MultiTouchInput fingersUp = secondFingerDown;
-  fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
-  manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
-
-  // Allow any animations to run their course.
-  child->AdvanceAnimationsUntilEnd();
-  rootApzc->AdvanceAnimationsUntilEnd();
-
-  // Make sure nothing is overscrolled.
-  EXPECT_FALSE(child->IsOverscrolled());
-  EXPECT_FALSE(rootApzc->IsOverscrolled());
-}
-
-// This is almost exactly like StuckInOverscroll_Bug1073250, except the
-// APZC receiving the input events for the first touch block is the child
-// (and thus not the same APZC that overscrolls, which is the parent).
-TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) {
-  // Enable overscrolling.
-  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
-
-  CreateOverscrollHandoffLayerTree1();
-
-  TestAsyncPanZoomController* child = ApzcOf(layers[1]);
-
-  // Pan, causing the parent APZC to overscroll.
-  Pan(manager, mcc, 60, 90, true /* keep finger down */);
-  EXPECT_FALSE(child->IsOverscrolled());
-  EXPECT_TRUE(rootApzc->IsOverscrolled());
-
-  // Put a second finger down.
-  MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
-  // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
-  secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
-  secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
-  manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
-
-  // Release the fingers.
-  MultiTouchInput fingersUp = secondFingerDown;
-  fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
-  manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
-
-  // Allow any animations to run their course.
-  child->AdvanceAnimationsUntilEnd();
-  rootApzc->AdvanceAnimationsUntilEnd();
-
-  // Make sure nothing is overscrolled.
-  EXPECT_FALSE(child->IsOverscrolled());
-  EXPECT_FALSE(rootApzc->IsOverscrolled());
-}
-
-// Test that flinging in a direction where one component of the fling goes into
-// overscroll but the other doesn't, results in just the one component being
-// handed off to the parent, while the original APZC continues flinging in the
-// other direction.
-TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) {
-  CreateOverscrollHandoffLayerTree1();
-
-  // Fling up and to the left. The child APZC has room to scroll up, but not
-  // to the left, so the horizontal component of the fling should be handed
-  // off to the parent APZC.
-  Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55));
-
-  RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root);
-  RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]);
-
-  // Advance the child's fling animation once to give the partial handoff
-  // a chance to occur.
-  mcc->AdvanceByMillis(10);
-  child->AdvanceAnimations(mcc->Time());
-
-  // Assert that partial handoff has occurred.
-  child->AssertStateIsFling();
-  parent->AssertStateIsFling();
-}
-
-// Here we test that if two flings are happening simultaneously, overscroll
-// is handed off correctly for each.
-TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) {
-  // Set up an initial APZC tree.
-  CreateOverscrollHandoffLayerTree3();
-
-  RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]);
-  RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]);
-  RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]);
-  RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]);
-
-  // Pan on the lower child.
-  Pan(child2, mcc, 45, 5);
-
-  // Pan on the upper child.
-  Pan(child1, mcc, 95, 55);
-
-  // Check that child1 and child2 are in a FLING state.
-  child1->AssertStateIsFling();
-  child2->AssertStateIsFling();
-
-  // Advance the animations on child1 and child2 until their end.
-  child1->AdvanceAnimationsUntilEnd();
-  child2->AdvanceAnimationsUntilEnd();
-
-  // Check that the flings have been handed off to the parents.
-  child1->AssertStateIsReset();
-  parent1->AssertStateIsFling();
-  child2->AssertStateIsReset();
-  parent2->AssertStateIsFling();
-}
-
-TEST_F(APZOverscrollHandoffTester, Scrollgrab) {
-  // Set up the layer tree
-  CreateScrollgrabLayerTree();
-
-  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
-
-  // Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
-  // and leave some more (another 15 px) for the child.
-  Pan(childApzc, mcc, 80, 45);
-
-  // Check that the parent and child have scrolled as much as we expect.
-  EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y);
-}
-
-TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
-  // Set up the layer tree
-  CreateScrollgrabLayerTree();
-
-  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
-
-  // Pan on the child, not enough to fully scroll the scrollgrab parent.
-  Pan(childApzc, mcc, 80, 70);
-
-  // Check that it is the scrollgrab parent that's in a fling, not the child.
-  rootApzc->AssertStateIsFling();
-  childApzc->AssertStateIsReset();
-}
-
-TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) {
-  CreateScrollgrabLayerTree(true /* make parent scrollable */);
-  TestFlingAcceleration();
-}
-
-TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) {
-  CreateScrollgrabLayerTree(false /* do not make parent scrollable */);
-  TestFlingAcceleration();
-}
-
-TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) {
-  SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
-
-  CreateOverscrollHandoffLayerTree1();
-
-  RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
-  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
-
-  // Pan on the child, enough to scroll it to its end and have scroll
-  // left to hand off. Since immediate handoff is disallowed, we expect
-  // the leftover scroll not to be handed off.
-  Pan(childApzc, mcc, 60, 5);
-
-  // Verify that the parent has not scrolled.
-  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
-  EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
-
-  // Pan again on the child. This time, since the child was scrolled to
-  // its end when the gesture began, we expect the scroll to be handed off.
-  Pan(childApzc, mcc, 60, 50);
-
-  // Verify that the parent scrolled.
-  EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y);
-}
-
-TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) {
-  SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
-
-  CreateOverscrollHandoffLayerTree1();
-
-  RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
-  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
-
-  // Pan on the child, enough to get very close to the end, so that the
-  // subsequent fling reaches the end and has leftover velocity to hand off.
-  Pan(childApzc, mcc, 60, 12);
-
-  // Allow the fling to run its course.
-  childApzc->AdvanceAnimationsUntilEnd();
-  parentApzc->AdvanceAnimationsUntilEnd();
-
-  // Verify that the parent has not scrolled.
-  // The first comparison needs to be an ASSERT_NEAR because the fling
-  // computations are such that the final scroll position can be within
-  // COORDINATE_EPSILON of the end rather than right at the end.
-  ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON);
-  EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
-
-  // Pan again on the child. This time, since the child was scrolled to
-  // its end when the gesture began, we expect the scroll to be handed off.
-  Pan(childApzc, mcc, 60, 50);
-
-  // Allow the fling to run its course. The fling should also be handed off.
-  childApzc->AdvanceAnimationsUntilEnd();
-  parentApzc->AdvanceAnimationsUntilEnd();
-
-  // Verify that the parent scrolled from the fling.
-  EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10);
-}
-
-class APZEventRegionsTester : public APZCTreeManagerTester {
-protected:
-  UniquePtr<ScopedLayerTreeRegistration> registration;
-  TestAsyncPanZoomController* rootApzc;
-
-  void CreateEventRegionsLayerTree1() {
-    const char* layerTreeSyntax = "c(tt)";
-    nsIntRegion layerVisibleRegions[] = {
-      nsIntRegion(IntRect(0, 0, 200, 200)),     // root
-      nsIntRegion(IntRect(0, 0, 100, 200)),     // left half
-      nsIntRegion(IntRect(0, 100, 200, 100)),   // bottom half
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
-    SetScrollHandoff(layers[1], root);
-    SetScrollHandoff(layers[2], root);
-
-    // Set up the event regions over a 200x200 area. The root layer has the
-    // whole 200x200 as the hit region; layers[1] has the left half and
-    // layers[2] has the bottom half. The bottom-left 100x100 area is also
-    // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is
-    // on top so it gets the events by default if the main thread doesn't
-    // respond).
-    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
-    root->SetEventRegions(regions);
-    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100));
-    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200));
-    layers[1]->SetEventRegions(regions);
-    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
-    layers[2]->SetEventRegions(regions);
-
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-  }
-
-  void CreateEventRegionsLayerTree2() {
-    const char* layerTreeSyntax = "c(t)";
-    nsIntRegion layerVisibleRegions[] = {
-      nsIntRegion(IntRect(0, 0, 100, 500)),
-      nsIntRegion(IntRect(0, 150, 100, 100)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
-
-    // Set up the event regions so that the child thebes layer is positioned far
-    // away from the scrolling container layer.
-    EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
-    root->SetEventRegions(regions);
-    regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100));
-    layers[1]->SetEventRegions(regions);
-
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-  }
-
-  void CreateObscuringLayerTree() {
-    const char* layerTreeSyntax = "c(c(t)t)";
-    // LayerID                     0 1 2 3
-    // 0 is the root.
-    // 1 is a parent scrollable layer.
-    // 2 is a child scrollable layer.
-    // 3 is the Obscurer, who ruins everything.
-    nsIntRegion layerVisibleRegions[] = {
-        // x coordinates are uninteresting
-        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
-        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
-        nsIntRegion(IntRect(0, 100, 200,  50)),  // [100, 150]
-        nsIntRegion(IntRect(0, 100, 200, 100))   // [100, 200]
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
-
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
-    SetScrollHandoff(layers[2], layers[1]);
-    SetScrollHandoff(layers[1], root);
-
-    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
-    root->SetEventRegions(regions);
-    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300));
-    layers[1]->SetEventRegions(regions);
-    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
-    layers[2]->SetEventRegions(regions);
-
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-    rootApzc = ApzcOf(root);
-  }
-
-  void CreateBug1119497LayerTree() {
-    const char* layerTreeSyntax = "c(tt)";
-    // LayerID                     0 12
-    // 0 is the root and has an APZC
-    // 1 is behind 2 and has an APZC
-    // 2 entirely covers 1 and should take all the input events, but has no APZC
-    // so hits to 2 should go to to the root APZC
-    nsIntRegion layerVisibleRegions[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
-
-    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
-    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
-
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  }
-
-  void CreateBug1117712LayerTree() {
-    const char* layerTreeSyntax = "c(c(t)t)";
-    // LayerID                     0 1 2 3
-    // 0 is the root
-    // 1 is a container layer whose sole purpose to make a non-empty ancestor
-    //   transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
-    //   transforms are different from 3's.
-    // 2 is a small layer that is the actual target
-    // 3 is a big layer obscuring 2 with a dispatch-to-content region
-    nsIntRegion layerVisibleRegions[] = {
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-      nsIntRegion(IntRect(0, 0, 0, 0)),
-      nsIntRegion(IntRect(0, 0, 10, 10)),
-      nsIntRegion(IntRect(0, 0, 100, 100)),
-    };
-    Matrix4x4 layerTransforms[] = {
-      Matrix4x4(),
-      Matrix4x4::Translation(50, 0, 0),
-      Matrix4x4(),
-      Matrix4x4(),
-    };
-    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers);
-
-    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 10, 10));
-    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100));
-
-    EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10)));
-    layers[2]->SetEventRegions(regions);
-    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
-    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
-    layers[3]->SetEventRegions(regions);
-
-    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
-    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-  }
-};
-
-TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
-  CreateEventRegionsLayerTree1();
-
-  TestAsyncPanZoomController* root = ApzcOf(layers[0]);
-  TestAsyncPanZoomController* left = ApzcOf(layers[1]);
-  TestAsyncPanZoomController* bottom = ApzcOf(layers[2]);
-
-  MockFunction<void(std::string checkPointName)> check;
-  {
-    InSequence s;
-    EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped on left"));
-    EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped on bottom"));
-    EXPECT_CALL(*mcc, HandleSingleTap(_, _, root->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped on root"));
-    EXPECT_CALL(check, Call("Tap pending on d-t-c region"));
-    EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped on bottom again"));
-    EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
-    EXPECT_CALL(check, Call("Tapped on left this time"));
-  }
-
-  TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
-
-  // Tap in the exposed hit regions of each of the layers once and ensure
-  // the clicks are dispatched right away
-  Tap(manager, 10, 10, mcc, tapDuration);
-  mcc->RunThroughDelayedTasks();    // this runs the tap event
-  check.Call("Tapped on left");
-  Tap(manager, 110, 110, mcc, tapDuration);
-  mcc->RunThroughDelayedTasks();    // this runs the tap event
-  check.Call("Tapped on bottom");
-  Tap(manager, 110, 10, mcc, tapDuration);
-  mcc->RunThroughDelayedTasks();    // this runs the tap event
-  check.Call("Tapped on root");
-
-  // Now tap on the dispatch-to-content region where the layers overlap
-  Tap(manager, 10, 110, mcc, tapDuration);
-  mcc->RunThroughDelayedTasks();    // this runs the main-thread timeout
-  check.Call("Tap pending on d-t-c region");
-  mcc->RunThroughDelayedTasks();    // this runs the tap event
-  check.Call("Tapped on bottom again");
-
-  // Now let's do that again, but simulate a main-thread response
-  uint64_t inputBlockId = 0;
-  Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId);
-  nsTArray<ScrollableLayerGuid> targets;
-  targets.AppendElement(left->GetGuid());
-  manager->SetTargetAPZC(inputBlockId, targets);
-  while (mcc->RunThroughDelayedTasks());    // this runs the tap event
-  check.Call("Tapped on left this time");
-}
-
-TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) {
-  CreateEventRegionsLayerTree2();
-
-  // Tap in the area of the child layer that's not directly included in the
-  // parent layer's hit region. Verify that it comes out of the APZC's
-  // content controller, which indicates the input events got routed correctly
-  // to the APZC.
-  EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1);
-  Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100));
-}
-
-TEST_F(APZEventRegionsTester, Obscuration) {
-  CreateObscuringLayerTree();
-  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
-
-  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
-
-  TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
-  TestAsyncPanZoomController* child = ApzcOf(layers[2]);
-
-  ApzcPanNoFling(parent, mcc, 75, 25);
-
-  HitTestResult result;
-  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
-  EXPECT_EQ(child, hit.get());
-  EXPECT_EQ(HitTestResult::HitLayer, result);
-}
-
-TEST_F(APZEventRegionsTester, Bug1119497) {
-  CreateBug1119497LayerTree();
-
-  HitTestResult result;
-  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
-  // We should hit layers[2], so |result| will be HitLayer but there's no
-  // actual APZC on layers[2], so it will be the APZC of the root layer.
-  EXPECT_EQ(ApzcOf(layers[0]), hit.get());
-  EXPECT_EQ(HitTestResult::HitLayer, result);
-}
-
-TEST_F(APZEventRegionsTester, Bug1117712) {
-  CreateBug1117712LayerTree();
-
-  TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
-
-  // These touch events should hit the dispatch-to-content region of layers[3]
-  // and so get queued with that APZC as the tentative target.
-  uint64_t inputBlockId = 0;
-  Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId);
-  // But now we tell the APZ that really it hit layers[2], and expect the tap
-  // to be delivered at the correct coordinates.
-  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1);
-
-  nsTArray<ScrollableLayerGuid> targets;
-  targets.AppendElement(apzc2->GetGuid());
-  manager->SetTargetAPZC(inputBlockId, targets);
-}
-
-class TaskRunMetrics {
-public:
-  TaskRunMetrics()
-    : mRunCount(0)
-    , mCancelCount(0)
-  {}
-
-  void IncrementRunCount() {
-    mRunCount++;
-  }
-
-  void IncrementCancelCount() {
-    mCancelCount++;
-  }
-
-  int GetAndClearRunCount() {
-    int runCount = mRunCount;
-    mRunCount = 0;
-    return runCount;
-  }
-
-  int GetAndClearCancelCount() {
-    int cancelCount = mCancelCount;
-    mCancelCount = 0;
-    return cancelCount;
-  }
-
-private:
-  int mRunCount;
-  int mCancelCount;
-};
-
-class MockTask : public CancelableTask {
-public:
-  explicit MockTask(TaskRunMetrics& aMetrics)
-    : mMetrics(aMetrics)
-  {}
-
-  virtual void Run() {
-    mMetrics.IncrementRunCount();
-  }
-
-  virtual void Cancel() {
-    mMetrics.IncrementCancelCount();
-  }
-
-private:
-  TaskRunMetrics& mMetrics;
-};
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestBasic.cpp
@@ -0,0 +1,348 @@
+/* -*- 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 "APZCBasicTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+TEST_F(APZCBasicTester, Overzoom) {
+  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
+  FrameMetrics fm;
+  fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+  fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
+  fm.SetScrollOffset(CSSPoint(10, 0));
+  fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
+  fm.SetIsRootContent(true);
+  apzc->SetFrameMetrics(fm);
+
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true);
+
+  fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
+  // bug 936721 - PGO builds introduce rounding error so
+  // use a fuzzy match instead
+  EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
+  EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
+}
+
+TEST_F(APZCBasicTester, SimpleTransform) {
+  ParentLayerPoint pointOut;
+  AsyncTransform viewTransformOut;
+  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+
+  EXPECT_EQ(ParentLayerPoint(), pointOut);
+  EXPECT_EQ(AsyncTransform(), viewTransformOut);
+}
+
+
+TEST_F(APZCBasicTester, ComplexTransform) {
+  // This test assumes there is a page that gets rendered to
+  // two layers. In CSS pixels, the first layer is 50x50 and
+  // the second layer is 25x50. The widget scale factor is 3.0
+  // and the presShell resolution is 2.0. Therefore, these layers
+  // end up being 300x300 and 150x300 in layer pixels.
+  //
+  // The second (child) layer has an additional CSS transform that
+  // stretches it by 2.0 on the x-axis. Therefore, after applying
+  // CSS transforms, the two layers are the same size in screen
+  // pixels.
+  //
+  // The screen itself is 24x24 in screen pixels (therefore 4x4 in
+  // CSS pixels). The displayport is 1 extra CSS pixel on all
+  // sides.
+
+  RefPtr<TestAsyncPanZoomController> childApzc =
+      new TestAsyncPanZoomController(0, mcc, tm);
+
+  const char* layerTreeSyntax = "c(c)";
+  // LayerID                     0 1
+  nsIntRegion layerVisibleRegion[] = {
+    nsIntRegion(IntRect(0, 0, 300, 300)),
+    nsIntRegion(IntRect(0, 0, 150, 300)),
+  };
+  Matrix4x4 transforms[] = {
+    Matrix4x4(),
+    Matrix4x4(),
+  };
+  transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
+  transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
+
+  nsTArray<RefPtr<Layer> > layers;
+  RefPtr<LayerManager> lm;
+  RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
+
+  FrameMetrics metrics;
+  metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
+  metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
+  metrics.SetScrollOffset(CSSPoint(10, 10));
+  metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
+  metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
+  metrics.SetPresShellResolution(2.0f);
+  metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
+  metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
+  metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);
+
+  FrameMetrics childMetrics = metrics;
+  childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);
+
+  layers[0]->SetFrameMetrics(metrics);
+  layers[1]->SetFrameMetrics(childMetrics);
+
+  ParentLayerPoint pointOut;
+  AsyncTransform viewTransformOut;
+
+  // Both the parent and child layer should behave exactly the same here, because
+  // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code
+
+  // initial transform
+  apzc->SetFrameMetrics(metrics);
+  apzc->NotifyLayersUpdated(metrics, true, true);
+  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
+
+  childApzc->SetFrameMetrics(childMetrics);
+  childApzc->NotifyLayersUpdated(childMetrics, true, true);
+  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
+
+  // do an async scroll by 5 pixels and check the transform
+  metrics.ScrollBy(CSSPoint(5, 0));
+  apzc->SetFrameMetrics(metrics);
+  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
+
+  childMetrics.ScrollBy(CSSPoint(5, 0));
+  childApzc->SetFrameMetrics(childMetrics);
+  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
+
+  // do an async zoom of 1.5x and check the transform
+  metrics.ZoomBy(1.5f);
+  apzc->SetFrameMetrics(metrics);
+  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
+
+  childMetrics.ZoomBy(1.5f);
+  childApzc->SetFrameMetrics(childMetrics);
+  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
+  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
+
+  childApzc->Destroy();
+}
+
+TEST_F(APZCBasicTester, Fling) {
+  int touchStart = 50;
+  int touchEnd = 10;
+  ParentLayerPoint pointOut;
+  AsyncTransform viewTransformOut;
+
+  // Fling down. Each step scroll further down
+  Pan(apzc, mcc, touchStart, touchEnd);
+  ParentLayerPoint lastPoint;
+  for (int i = 1; i < 50; i+=1) {
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1));
+    EXPECT_GT(pointOut.y, lastPoint.y);
+    lastPoint = pointOut;
+  }
+}
+
+TEST_F(APZCBasicTester, FlingIntoOverscroll) {
+  // Enable overscrolling.
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Scroll down by 25 px. Don't fling for simplicity.
+  ApzcPanNoFling(apzc, mcc, 50, 25);
+
+  // Now scroll back up by 20px, this time flinging after.
+  // The fling should cover the remaining 5 px of room to scroll, then
+  // go into overscroll, and finally snap-back to recover from overscroll.
+  Pan(apzc, mcc, 25, 45);
+  const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+  bool reachedOverscroll = false;
+  bool recoveredFromOverscroll = false;
+  while (apzc->AdvanceAnimations(mcc->Time())) {
+    if (!reachedOverscroll && apzc->IsOverscrolled()) {
+      reachedOverscroll = true;
+    }
+    if (reachedOverscroll && !apzc->IsOverscrolled()) {
+      recoveredFromOverscroll = true;
+    }
+    mcc->AdvanceBy(increment);
+  }
+  EXPECT_TRUE(reachedOverscroll);
+  EXPECT_TRUE(recoveredFromOverscroll);
+}
+
+TEST_F(APZCBasicTester, PanningTransformNotifications) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Scroll down by 25 px. Ensure we only get one set of
+  // state change notifications.
+  //
+  // Then, scroll back up by 20px, this time flinging after.
+  // The fling should cover the remaining 5 px of room to scroll, then
+  // go into overscroll, and finally snap-back to recover from overscroll.
+  // Again, ensure we only get one set of state change notifications for
+  // this entire procedure.
+
+  MockFunction<void(std::string checkPointName)> check;
+  {
+    InSequence s;
+    EXPECT_CALL(check, Call("Simple pan"));
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
+    EXPECT_CALL(check, Call("Complex pan"));
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformBegin,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::StartPanning,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::EndTouch,_)).Times(1);
+    EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::TransformEnd,_)).Times(1);
+    EXPECT_CALL(check, Call("Done"));
+  }
+
+  check.Call("Simple pan");
+  ApzcPanNoFling(apzc, mcc, 50, 25);
+  check.Call("Complex pan");
+  Pan(apzc, mcc, 25, 45);
+  apzc->AdvanceAnimationsUntilEnd();
+  check.Call("Done");
+}
+
+void APZCBasicTester::PanIntoOverscroll()
+{
+  int touchStart = 500;
+  int touchEnd = 10;
+  Pan(apzc, mcc, touchStart, touchEnd);
+  EXPECT_TRUE(apzc->IsOverscrolled());
+}
+
+void APZCBasicTester::TestOverscroll()
+{
+  // Pan sufficiently to hit overscroll behavior
+  PanIntoOverscroll();
+
+  // Check that we recover from overscroll via an animation.
+  ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+  SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+
+
+TEST_F(APZCBasicTester, OverScrollPanning) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  TestOverscroll();
+}
+
+// Tests that an overscroll animation doesn't trigger an assertion failure
+// in the case where a sample has a velocity of zero.
+TEST_F(APZCBasicTester, OverScroll_Bug1152051a) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Doctor the prefs to make the velocity zero at the end of the first sample.
+
+  // This ensures our incoming velocity to the overscroll animation is
+  // a round(ish) number, 4.9 (that being the distance of the pan before
+  // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
+  // the pan, which is 100 ms).
+  SCOPED_GFX_PREF(APZFlingFriction, float, 0);
+
+  // To ensure the velocity after the first sample is 0, set the spring
+  // stiffness to the incoming velocity (4.9) divided by the overscroll
+  // (400 pixels) times the step duration (1 ms).
+  SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f);
+
+  TestOverscroll();
+}
+
+// Tests that ending an overscroll animation doesn't leave around state that
+// confuses the next overscroll animation.
+TEST_F(APZCBasicTester, OverScroll_Bug1152051b) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f);
+
+  // Pan sufficiently to hit overscroll behavior
+  PanIntoOverscroll();
+
+  // Sample animations once, to give the fling animation started on touch-up
+  // a chance to realize it's overscrolled, and schedule a call to
+  // HandleFlingOverscroll().
+  SampleAnimationOnce();
+
+  // This advances the time and runs the HandleFlingOverscroll task scheduled in
+  // the previous call, which starts an overscroll animation. It then samples
+  // the overscroll animation once, to get it to initialize the first overscroll
+  // sample.
+  SampleAnimationOnce();
+
+  // Do a touch-down to cancel the overscroll animation, and then a touch-up
+  // to schedule a new one since we're still overscrolled. We don't pan because
+  // panning can trigger functions that clear the overscroll animation state
+  // in other ways.
+  TouchDown(apzc, 10, 10, mcc->Time(), nullptr);
+  TouchUp(apzc, 10, 10, mcc->Time());
+
+  // Sample the second overscroll animation to its end.
+  // If the ending of the first overscroll animation fails to clear state
+  // properly, this will assert.
+  ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+  SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+
+TEST_F(APZCBasicTester, OverScrollAbort) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Pan sufficiently to hit overscroll behavior
+  int touchStart = 500;
+  int touchEnd = 10;
+  Pan(apzc, mcc, touchStart, touchEnd);
+  EXPECT_TRUE(apzc->IsOverscrolled());
+
+  ParentLayerPoint pointOut;
+  AsyncTransform viewTransformOut;
+
+  // This sample call will run to the end of the fling animation
+  // and will schedule the overscroll animation.
+  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000));
+  EXPECT_TRUE(apzc->IsOverscrolled());
+
+  // At this point, we have an active overscroll animation.
+  // Check that cancelling the animation clears the overscroll.
+  apzc->CancelAnimation();
+  EXPECT_FALSE(apzc->IsOverscrolled());
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCBasicTester, OverScrollPanningAbort) {
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  // Pan sufficiently to hit overscroll behaviour. Keep the finger down so
+  // the pan does not end.
+  int touchStart = 500;
+  int touchEnd = 10;
+  Pan(apzc, mcc, touchStart, touchEnd, true); // keep finger down
+  EXPECT_TRUE(apzc->IsOverscrolled());
+
+  // Check that calling CancelAnimation() while the user is still panning
+  // (and thus no fling or snap-back animation has had a chance to start)
+  // clears the overscroll.
+  apzc->CancelAnimation();
+  EXPECT_FALSE(apzc->IsOverscrolled());
+  apzc->AssertStateIsReset();
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp
@@ -0,0 +1,271 @@
+/* -*- 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 "APZCTreeManagerTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+class APZEventRegionsTester : public APZCTreeManagerTester {
+protected:
+  UniquePtr<ScopedLayerTreeRegistration> registration;
+  TestAsyncPanZoomController* rootApzc;
+
+  void CreateEventRegionsLayerTree1() {
+    const char* layerTreeSyntax = "c(tt)";
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(IntRect(0, 0, 200, 200)),     // root
+      nsIntRegion(IntRect(0, 0, 100, 200)),     // left half
+      nsIntRegion(IntRect(0, 100, 200, 100)),   // bottom half
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
+    SetScrollHandoff(layers[1], root);
+    SetScrollHandoff(layers[2], root);
+
+    // Set up the event regions over a 200x200 area. The root layer has the
+    // whole 200x200 as the hit region; layers[1] has the left half and
+    // layers[2] has the bottom half. The bottom-left 100x100 area is also
+    // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is
+    // on top so it gets the events by default if the main thread doesn't
+    // respond).
+    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
+    root->SetEventRegions(regions);
+    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100));
+    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200));
+    layers[1]->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
+    layers[2]->SetEventRegions(regions);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateEventRegionsLayerTree2() {
+    const char* layerTreeSyntax = "c(t)";
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(IntRect(0, 0, 100, 500)),
+      nsIntRegion(IntRect(0, 150, 100, 100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
+
+    // Set up the event regions so that the child thebes layer is positioned far
+    // away from the scrolling container layer.
+    EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
+    root->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100));
+    layers[1]->SetEventRegions(regions);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateObscuringLayerTree() {
+    const char* layerTreeSyntax = "c(c(t)t)";
+    // LayerID                     0 1 2 3
+    // 0 is the root.
+    // 1 is a parent scrollable layer.
+    // 2 is a child scrollable layer.
+    // 3 is the Obscurer, who ruins everything.
+    nsIntRegion layerVisibleRegions[] = {
+        // x coordinates are uninteresting
+        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
+        nsIntRegion(IntRect(0,   0, 200, 200)),  // [0, 200]
+        nsIntRegion(IntRect(0, 100, 200,  50)),  // [100, 150]
+        nsIntRegion(IntRect(0, 100, 200, 100))   // [100, 200]
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
+    SetScrollHandoff(layers[2], layers[1]);
+    SetScrollHandoff(layers[1], root);
+
+    EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
+    root->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300));
+    layers[1]->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
+    layers[2]->SetEventRegions(regions);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateBug1119497LayerTree() {
+    const char* layerTreeSyntax = "c(tt)";
+    // LayerID                     0 12
+    // 0 is the root and has an APZC
+    // 1 is behind 2 and has an APZC
+    // 2 entirely covers 1 and should take all the input events, but has no APZC
+    // so hits to 2 should go to to the root APZC
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
+
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  }
+
+  void CreateBug1117712LayerTree() {
+    const char* layerTreeSyntax = "c(c(t)t)";
+    // LayerID                     0 1 2 3
+    // 0 is the root
+    // 1 is a container layer whose sole purpose to make a non-empty ancestor
+    //   transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
+    //   transforms are different from 3's.
+    // 2 is a small layer that is the actual target
+    // 3 is a big layer obscuring 2 with a dispatch-to-content region
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 0, 0, 0)),
+      nsIntRegion(IntRect(0, 0, 10, 10)),
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+    };
+    Matrix4x4 layerTransforms[] = {
+      Matrix4x4(),
+      Matrix4x4::Translation(50, 0, 0),
+      Matrix4x4(),
+      Matrix4x4(),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers);
+
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 10, 10));
+    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100));
+
+    EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10)));
+    layers[2]->SetEventRegions(regions);
+    regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
+    regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
+    layers[3]->SetEventRegions(regions);
+
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  }
+};
+
+TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
+  CreateEventRegionsLayerTree1();
+
+  TestAsyncPanZoomController* root = ApzcOf(layers[0]);
+  TestAsyncPanZoomController* left = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* bottom = ApzcOf(layers[2]);
+
+  MockFunction<void(std::string checkPointName)> check;
+  {
+    InSequence s;
+    EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped on left"));
+    EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped on bottom"));
+    EXPECT_CALL(*mcc, HandleSingleTap(_, _, root->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped on root"));
+    EXPECT_CALL(check, Call("Tap pending on d-t-c region"));
+    EXPECT_CALL(*mcc, HandleSingleTap(_, _, bottom->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped on bottom again"));
+    EXPECT_CALL(*mcc, HandleSingleTap(_, _, left->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped on left this time"));
+  }
+
+  TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
+
+  // Tap in the exposed hit regions of each of the layers once and ensure
+  // the clicks are dispatched right away
+  Tap(manager, 10, 10, mcc, tapDuration);
+  mcc->RunThroughDelayedTasks();    // this runs the tap event
+  check.Call("Tapped on left");
+  Tap(manager, 110, 110, mcc, tapDuration);
+  mcc->RunThroughDelayedTasks();    // this runs the tap event
+  check.Call("Tapped on bottom");
+  Tap(manager, 110, 10, mcc, tapDuration);
+  mcc->RunThroughDelayedTasks();    // this runs the tap event
+  check.Call("Tapped on root");
+
+  // Now tap on the dispatch-to-content region where the layers overlap
+  Tap(manager, 10, 110, mcc, tapDuration);
+  mcc->RunThroughDelayedTasks();    // this runs the main-thread timeout
+  check.Call("Tap pending on d-t-c region");
+  mcc->RunThroughDelayedTasks();    // this runs the tap event
+  check.Call("Tapped on bottom again");
+
+  // Now let's do that again, but simulate a main-thread response
+  uint64_t inputBlockId = 0;
+  Tap(manager, 10, 110, mcc, tapDuration, nullptr, &inputBlockId);
+  nsTArray<ScrollableLayerGuid> targets;
+  targets.AppendElement(left->GetGuid());
+  manager->SetTargetAPZC(inputBlockId, targets);
+  while (mcc->RunThroughDelayedTasks());    // this runs the tap event
+  check.Call("Tapped on left this time");
+}
+
+TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) {
+  CreateEventRegionsLayerTree2();
+
+  // Tap in the area of the child layer that's not directly included in the
+  // parent layer's hit region. Verify that it comes out of the APZC's
+  // content controller, which indicates the input events got routed correctly
+  // to the APZC.
+  EXPECT_CALL(*mcc, HandleSingleTap(_, _, rootApzc->GetGuid())).Times(1);
+  Tap(manager, 10, 160, mcc, TimeDuration::FromMilliseconds(100));
+}
+
+TEST_F(APZEventRegionsTester, Obscuration) {
+  CreateObscuringLayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* child = ApzcOf(layers[2]);
+
+  ApzcPanNoFling(parent, mcc, 75, 25);
+
+  HitTestResult result;
+  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
+  EXPECT_EQ(child, hit.get());
+  EXPECT_EQ(HitTestResult::HitLayer, result);
+}
+
+TEST_F(APZEventRegionsTester, Bug1119497) {
+  CreateBug1119497LayerTree();
+
+  HitTestResult result;
+  RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
+  // We should hit layers[2], so |result| will be HitLayer but there's no
+  // actual APZC on layers[2], so it will be the APZC of the root layer.
+  EXPECT_EQ(ApzcOf(layers[0]), hit.get());
+  EXPECT_EQ(HitTestResult::HitLayer, result);
+}
+
+TEST_F(APZEventRegionsTester, Bug1117712) {
+  CreateBug1117712LayerTree();
+
+  TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
+
+  // These touch events should hit the dispatch-to-content region of layers[3]
+  // and so get queued with that APZC as the tentative target.
+  uint64_t inputBlockId = 0;
+  Tap(manager, 55, 5, mcc, TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId);
+  // But now we tell the APZ that really it hit layers[2], and expect the tap
+  // to be delivered at the correct coordinates.
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(55, 5), 0, apzc2->GetGuid())).Times(1);
+
+  nsTArray<ScrollableLayerGuid> targets;
+  targets.AppendElement(apzc2->GetGuid());
+  manager->SetTargetAPZC(inputBlockId, targets);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp
@@ -0,0 +1,594 @@
+/* -*- 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 "APZCBasicTester.h"
+#include "APZTestCommon.h"
+
+class APZCGestureDetectorTester : public APZCBasicTester {
+public:
+  APZCGestureDetectorTester()
+    : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
+  {
+  }
+
+protected:
+  FrameMetrics GetPinchableFrameMetrics()
+  {
+    FrameMetrics fm;
+    fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
+    fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
+    fm.SetScrollOffset(CSSPoint(300, 300));
+    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
+    // APZC only allows zooming on the root scrollable frame.
+    fm.SetIsRootContent(true);
+    // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
+    return fm;
+  }
+};
+
+TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+
+  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
+  apzc->SetFrameMetrics(originalMetrics);
+
+  MakeApzcZoomable();
+
+  // Test parameters
+  float zoomAmount = 1.25;
+  float pinchLength = 100.0;
+  float pinchLengthScaled = pinchLength * zoomAmount;
+  int focusX = 250;
+  int focusY = 300;
+  int panDistance = 20;
+
+  int firstFingerId = 0;
+  int secondFingerId = firstFingerId + 1;
+
+  // Put fingers down
+  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY));
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Spread fingers out to enter the pinch state
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY));
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Do the actual pinch of 1.25x
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Verify that the zoom changed, just to make sure our code above did what it
+  // was supposed to.
+  FrameMetrics zoomedMetrics = apzc->GetFrameMetrics();
+  float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale;
+  EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom);
+
+  // Now we lift one finger...
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // ... and pan with the remaining finger. This pan just breaks through the
+  // distance threshold.
+  focusY += 40;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // This one does an actual pan of 20 pixels
+  focusY += panDistance;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Lift the remaining finger
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Verify that we scrolled
+  FrameMetrics finalMetrics = apzc->GetFrameMetrics();
+  EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y);
+
+  // Clear out any remaining fling animation and pending tasks
+  apzc->AdvanceAnimationsUntilEnd();
+  while (mcc->RunThroughDelayedTasks());
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, Pan_With_Tap) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+
+  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
+  apzc->SetFrameMetrics(originalMetrics);
+
+  // Making the APZC zoomable isn't really needed for the correct operation of
+  // this test, but it could help catch regressions where we accidentally enter
+  // a pinch state.
+  MakeApzcZoomable();
+
+  // Test parameters
+  int touchX = 250;
+  int touchY = 300;
+  int panDistance = 20;
+
+  int firstFingerId = 0;
+  int secondFingerId = firstFingerId + 1;
+
+  // Put finger down
+  MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Start a pan, break through the threshold
+  touchY += 40;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Do an actual pan for a bit
+  touchY += panDistance;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Put a second finger down
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Lift the second finger
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Bust through the threshold again
+  touchY += 40;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Do some more actual panning
+  touchY += panDistance;
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Lift the first finger
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
+  mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  // Verify that we scrolled
+  FrameMetrics finalMetrics = apzc->GetFrameMetrics();
+  float zoom = finalMetrics.GetZoom().ToScaleFactor().scale;
+  EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y);
+
+  // Clear out any remaining fling animation and pending tasks
+  apzc->AdvanceAnimationsUntilEnd();
+  while (mcc->RunThroughDelayedTasks());
+  apzc->AssertStateIsReset();
+}
+
+class APZCFlingStopTester : public APZCGestureDetectorTester {
+protected:
+  // Start a fling, and then tap while the fling is ongoing. When
+  // aSlow is false, the tap will happen while the fling is at a
+  // high velocity, and we check that the tap doesn't trigger sending a tap
+  // to content. If aSlow is true, the tap will happen while the fling
+  // is at a slow velocity, and we check that the tap does trigger sending
+  // a tap to content. See bug 1022956.
+  void DoFlingStopTest(bool aSlow) {
+    int touchStart = 50;
+    int touchEnd = 10;
+
+    // Start the fling down.
+    Pan(apzc, mcc, touchStart, touchEnd);
+    // The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
+
+    // If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
+    // the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
+    // friction and threshold values change, but they should be deterministic at least.
+    int timeDelta = aSlow ? 2000 : 10;
+    int tapCallsExpected = aSlow ? 2 : 1;
+
+    // Advance the fling animation by timeDelta milliseconds.
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta));
+
+    // Deliver a tap to abort the fling. Ensure that we get a HandleSingleTap
+    // call out of it if and only if the fling is slow.
+    EXPECT_CALL(*mcc, HandleSingleTap(_, 0, apzc->GetGuid())).Times(tapCallsExpected);
+    Tap(apzc, 10, 10, mcc, 0);
+    while (mcc->RunThroughDelayedTasks());
+
+    // Deliver another tap, to make sure that taps are flowing properly once
+    // the fling is aborted.
+    Tap(apzc, 100, 100, mcc, 0);
+    while (mcc->RunThroughDelayedTasks());
+
+    // Verify that we didn't advance any further after the fling was aborted, in either case.
+    ParentLayerPoint finalPointOut;
+    apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut);
+    EXPECT_EQ(pointOut.x, finalPointOut.x);
+    EXPECT_EQ(pointOut.y, finalPointOut.y);
+
+    apzc->AssertStateIsReset();
+  }
+
+  void DoFlingStopWithSlowListener(bool aPreventDefault) {
+    MakeApzcWaitForMainThread();
+
+    int touchStart = 50;
+    int touchEnd = 10;
+    uint64_t blockId = 0;
+
+    // Start the fling down.
+    Pan(apzc, mcc, touchStart, touchEnd, false, nullptr, nullptr, &blockId);
+    apzc->ConfirmTarget(blockId);
+    apzc->ContentReceivedInputBlock(blockId, false);
+
+    // Sample the fling a couple of times to ensure it's going.
+    ParentLayerPoint point, finalPoint;
+    AsyncTransform viewTransform;
+    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
+    apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10));
+    EXPECT_GT(finalPoint.y, point.y);
+
+    // Now we put our finger down to stop the fling
+    TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
+
+    // Re-sample to make sure it hasn't moved
+    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
+    EXPECT_EQ(finalPoint.x, point.x);
+    EXPECT_EQ(finalPoint.y, point.y);
+
+    // respond to the touchdown that stopped the fling.
+    // even if we do a prevent-default on it, the animation should remain stopped.
+    apzc->ContentReceivedInputBlock(blockId, aPreventDefault);
+
+    // Verify the page hasn't moved
+    apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70));
+    EXPECT_EQ(finalPoint.x, point.x);
+    EXPECT_EQ(finalPoint.y, point.y);
+
+    // clean up
+    TouchUp(apzc, 10, 10, mcc->Time());
+
+    apzc->AssertStateIsReset();
+  }
+};
+
+TEST_F(APZCFlingStopTester, FlingStop) {
+  DoFlingStopTest(false);
+}
+
+TEST_F(APZCFlingStopTester, FlingStopTap) {
+  DoFlingStopTest(true);
+}
+
+TEST_F(APZCFlingStopTester, FlingStopSlowListener) {
+  DoFlingStopWithSlowListener(false);
+}
+
+TEST_F(APZCFlingStopTester, FlingStopPreventDefault) {
+  DoFlingStopWithSlowListener(true);
+}
+
+TEST_F(APZCGestureDetectorTester, ShortPress) {
+  MakeApzcUnzoomable();
+
+  MockFunction<void(std::string checkPointName)> check;
+  {
+    InSequence s;
+    // This verifies that the single tap notification is sent after the
+    // touchup is fully processed. The ordering here is important.
+    EXPECT_CALL(check, Call("pre-tap"));
+    EXPECT_CALL(check, Call("post-tap"));
+    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+  }
+
+  check.Call("pre-tap");
+  TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
+  check.Call("post-tap");
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, MediumPress) {
+  MakeApzcUnzoomable();
+
+  MockFunction<void(std::string checkPointName)> check;
+  {
+    InSequence s;
+    // This verifies that the single tap notification is sent after the
+    // touchup is fully processed. The ordering here is important.
+    EXPECT_CALL(check, Call("pre-tap"));
+    EXPECT_CALL(check, Call("post-tap"));
+    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+  }
+
+  check.Call("pre-tap");
+  TapAndCheckStatus(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(400));
+  check.Call("post-tap");
+
+  apzc->AssertStateIsReset();
+}
+
+class APZCLongPressTester : public APZCGestureDetectorTester {
+protected:
+  void DoLongPressTest(uint32_t aBehavior) {
+    MakeApzcUnzoomable();
+
+    uint64_t blockId = 0;
+
+    nsEventStatus status = TouchDown(apzc, 10, 10, mcc->Time(), &blockId);
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
+
+    if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
+      // SetAllowedTouchBehavior() must be called after sending touch-start.
+      nsTArray<uint32_t> allowedTouchBehaviors;
+      allowedTouchBehaviors.AppendElement(aBehavior);
+      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
+    }
+    // Have content "respond" to the touchstart
+    apzc->ContentReceivedInputBlock(blockId, false);
+
+    MockFunction<void(std::string checkPointName)> check;
+
+    {
+      InSequence s;
+
+      EXPECT_CALL(check, Call("preHandleLongTap"));
+      blockId++;
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1);
+      EXPECT_CALL(check, Call("postHandleLongTap"));
+
+      EXPECT_CALL(check, Call("preHandleSingleTap"));
+      EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+      EXPECT_CALL(check, Call("postHandleSingleTap"));
+    }
+
+    // Manually invoke the longpress while the touch is currently down.
+    check.Call("preHandleLongTap");
+    mcc->RunThroughDelayedTasks();
+    check.Call("postHandleLongTap");
+
+    // Dispatching the longpress event starts a new touch block, which
+    // needs a new content response and also has a pending timeout task
+    // in the queue. Deal with those here. We do the content response first
+    // with preventDefault=false, and then we run the timeout task which
+    // "loses the race" and does nothing.
+    apzc->ContentReceivedInputBlock(blockId, false);
+    mcc->AdvanceByMillis(1000);
+
+    // Finally, simulate lifting the finger. Since the long-press wasn't
+    // prevent-defaulted, we should get a long-tap-up event.
+    check.Call("preHandleSingleTap");
+    status = TouchUp(apzc, 10, 10, mcc->Time());
+    mcc->RunThroughDelayedTasks();
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
+    check.Call("postHandleSingleTap");
+
+    apzc->AssertStateIsReset();
+  }
+
+  void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
+    MakeApzcUnzoomable();
+
+    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+
+    int touchX = 10,
+        touchStartY = 10,
+        touchEndY = 50;
+
+    uint64_t blockId = 0;
+    nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId);
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
+
+    if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
+      // SetAllowedTouchBehavior() must be called after sending touch-start.
+      nsTArray<uint32_t> allowedTouchBehaviors;
+      allowedTouchBehaviors.AppendElement(aBehavior);
+      apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
+    }
+    // Have content "respond" to the touchstart
+    apzc->ContentReceivedInputBlock(blockId, false);
+
+    MockFunction<void(std::string checkPointName)> check;
+
+    {
+      InSequence s;
+
+      EXPECT_CALL(check, Call("preHandleLongTap"));
+      blockId++;
+      EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1);
+      EXPECT_CALL(check, Call("postHandleLongTap"));
+    }
+
+    // Manually invoke the longpress while the touch is currently down.
+    check.Call("preHandleLongTap");
+    mcc->RunThroughDelayedTasks();
+    check.Call("postHandleLongTap");
+
+    // There should be a TimeoutContentResponse task in the queue still,
+    // waiting for the response from the longtap event dispatched above.
+    // Send the signal that content has handled the long-tap, and then run
+    // the timeout task (it will be a no-op because the content "wins" the
+    // race. This takes the place of the "contextmenu" event.
+    apzc->ContentReceivedInputBlock(blockId, true);
+    mcc->AdvanceByMillis(1000);
+
+    MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time());
+    mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
+    status = apzc->ReceiveInputEvent(mti, nullptr);
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
+
+    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0);
+    status = TouchUp(apzc, touchX, touchEndY, mcc->Time());
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
+
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+
+    EXPECT_EQ(ParentLayerPoint(), pointOut);
+    EXPECT_EQ(AsyncTransform(), viewTransformOut);
+
+    apzc->AssertStateIsReset();
+  }
+};
+
+TEST_F(APZCLongPressTester, LongPress) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE);
+}
+
+TEST_F(APZCLongPressTester, LongPressWithTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
+                  | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
+                  | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
+}
+
+TEST_F(APZCLongPressTester, LongPressPreventDefault) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE);
+}
+
+TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
+                                | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
+                                | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
+}
+
+TEST_F(APZCGestureDetectorTester, DoubleTap) {
+  MakeApzcWaitForMainThread();
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
+  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+
+  uint64_t blockIds[2];
+  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
+
+  // responses to the two touchstarts
+  apzc->ContentReceivedInputBlock(blockIds[0], false);
+  apzc->ContentReceivedInputBlock(blockIds[1], false);
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
+  MakeApzcWaitForMainThread();
+  MakeApzcUnzoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(2);
+  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
+
+  uint64_t blockIds[2];
+  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
+
+  // responses to the two touchstarts
+  apzc->ContentReceivedInputBlock(blockIds[0], false);
+  apzc->ContentReceivedInputBlock(blockIds[1], false);
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
+  MakeApzcWaitForMainThread();
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
+
+  uint64_t blockIds[2];
+  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
+
+  // responses to the two touchstarts
+  apzc->ContentReceivedInputBlock(blockIds[0], true);
+  apzc->ContentReceivedInputBlock(blockIds[1], false);
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
+  MakeApzcWaitForMainThread();
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
+  EXPECT_CALL(*mcc, HandleDoubleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(0);
+
+  uint64_t blockIds[2];
+  DoubleTapAndCheckStatus(apzc, 10, 10, mcc, &blockIds);
+
+  // responses to the two touchstarts
+  apzc->ContentReceivedInputBlock(blockIds[0], true);
+  apzc->ContentReceivedInputBlock(blockIds[1], true);
+
+  apzc->AssertStateIsReset();
+}
+
+// Test for bug 947892
+// We test whether we dispatch tap event when the tap is followed by pinch.
+TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) {
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+
+  Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
+
+  int inputId = 0;
+  MultiTouchInput mti;
+  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
+  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
+  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) {
+  MakeApzcZoomable();
+
+  EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1);
+
+  Tap(apzc, 10, 10, mcc, TimeDuration::FromMilliseconds(100));
+
+  int inputId = 0;
+  MultiTouchInput mti;
+  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
+  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
+  mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
+  apzc->ReceiveInputEvent(mti, nullptr);
+
+  apzc->AssertStateIsReset();
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp
@@ -0,0 +1,487 @@
+/* -*- 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 "APZCTreeManagerTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+class APZHitTestingTester : public APZCTreeManagerTester {
+protected:
+  ScreenToParentLayerMatrix4x4 transformToApzc;
+  ParentLayerToScreenMatrix4x4 transformToGecko;
+
+  already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) {
+    RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr);
+    if (hit) {
+      transformToApzc = manager->GetScreenToApzcTransform(hit.get());
+      transformToGecko = manager->GetApzcToGeckoTransform(hit.get());
+    }
+    return hit.forget();
+  }
+
+protected:
+  void CreateHitTesting1LayerTree() {
+    const char* layerTreeSyntax = "c(tttt)";
+    // LayerID                     0 1234
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,100,100)),
+      nsIntRegion(IntRect(0,0,100,100)),
+      nsIntRegion(IntRect(10,10,20,20)),
+      nsIntRegion(IntRect(10,10,20,20)),
+      nsIntRegion(IntRect(5,5,20,20)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+  }
+
+  void CreateHitTesting2LayerTree() {
+    const char* layerTreeSyntax = "c(tc(t))";
+    // LayerID                     0 12 3
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,100,100)),
+      nsIntRegion(IntRect(10,10,40,40)),
+      nsIntRegion(IntRect(10,60,40,40)),
+      nsIntRegion(IntRect(10,60,40,40)),
+    };
+    Matrix4x4 transforms[] = {
+      Matrix4x4(),
+      Matrix4x4(),
+      Matrix4x4::Scaling(2, 1, 1),
+      Matrix4x4(),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
+
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
+    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80));
+  }
+
+  void CreateComplexMultiLayerTree() {
+    const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))";
+    // LayerID                     0 12 3 45 6 7 89
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,300,400)),      // root(0)
+      nsIntRegion(IntRect(0,0,100,100)),      // thebes(1) in top-left
+      nsIntRegion(IntRect(50,50,200,300)),    // container(2) centered in root(0)
+      nsIntRegion(IntRect(50,50,200,300)),    // thebes(3) fully occupying parent container(2)
+      nsIntRegion(IntRect(0,200,100,100)),    // thebes(4) in bottom-left
+      nsIntRegion(IntRect(200,0,100,400)),    // container(5) along the right 100px of root(0)
+      nsIntRegion(IntRect(200,0,100,200)),    // container(6) taking up the top half of parent container(5)
+      nsIntRegion(IntRect(200,0,100,200)),    // thebes(7) fully occupying parent container(6)
+      nsIntRegion(IntRect(200,200,100,100)),  // thebes(8) in bottom-right (below (6))
+      nsIntRegion(IntRect(200,300,100,100)),  // thebes(9) in bottom-right (below (8))
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
+    SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2);
+    SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1);
+    SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3);
+  }
+
+  void CreateBug1148350LayerTree() {
+    const char* layerTreeSyntax = "c(t)";
+    // LayerID                     0 1
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0,0,200,200)),
+      nsIntRegion(IntRect(0,0,200,200)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
+  }
+};
+
+// A simple hit testing test that doesn't involve any transforms on layers.
+TEST_F(APZHitTestingTester, HitTesting1) {
+  CreateHitTesting1LayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+
+  // No APZC attached so hit testing will return no APZC at (20,20)
+  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
+  TestAsyncPanZoomController* nullAPZC = nullptr;
+  EXPECT_EQ(nullAPZC, hit.get());
+  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
+  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
+
+  uint32_t paintSequenceNumber = 0;
+
+  // Now we have a root APZC that will match the page
+  SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
+  hit = GetTargetAPZC(ScreenPoint(15, 15));
+  EXPECT_EQ(ApzcOf(root), hit.get());
+  // expect hit point at LayerIntPoint(15, 15)
+  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
+  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
+
+  // Now we have a sub APZC with a better fit
+  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
+  EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
+  hit = GetTargetAPZC(ScreenPoint(25, 25));
+  EXPECT_EQ(ApzcOf(layers[3]), hit.get());
+  // expect hit point at LayerIntPoint(25, 25)
+  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
+  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
+
+  // At this point, layers[4] obscures layers[3] at the point (15, 15) so
+  // hitting there should hit the root APZC
+  hit = GetTargetAPZC(ScreenPoint(15, 15));
+  EXPECT_EQ(ApzcOf(root), hit.get());
+
+  // Now test hit testing when we have two scrollable layers
+  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++);
+  hit = GetTargetAPZC(ScreenPoint(15, 15));
+  EXPECT_EQ(ApzcOf(layers[4]), hit.get());
+  // expect hit point at LayerIntPoint(15, 15)
+  EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15));
+  EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15));
+
+  // Hit test ouside the reach of layer[3,4] but inside root
+  hit = GetTargetAPZC(ScreenPoint(90, 90));
+  EXPECT_EQ(ApzcOf(root), hit.get());
+  // expect hit point at LayerIntPoint(90, 90)
+  EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90));
+  EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90));
+
+  // Hit test ouside the reach of any layer
+  hit = GetTargetAPZC(ScreenPoint(1000, 10));
+  EXPECT_EQ(nullAPZC, hit.get());
+  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
+  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
+  hit = GetTargetAPZC(ScreenPoint(-1000, 10));
+  EXPECT_EQ(nullAPZC, hit.get());
+  EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
+  EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
+}
+
+// A more involved hit testing test that involves css and async transforms.
+TEST_F(APZHitTestingTester, HitTesting2) {
+  CreateHitTesting2LayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  // At this point, the following holds (all coordinates in screen pixels):
+  // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
+  // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
+  // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
+  // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
+
+  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
+  TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
+
+  // Hit an area that's clearly on the root layer but not any of the child layers.
+  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25));
+  EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25));
+
+  // Hit an area on the root that would be on layers[3] if layers[2]
+  // weren't transformed.
+  // Note that if layers[2] were scrollable, then this would hit layers[2]
+  // because its composition bounds would be at (10,60)-(50,100) (and the
+  // scale-only transform that we set on layers[2] would be invalid because
+  // it would place the layer into overscroll, as its composition bounds
+  // start at x=10 but its content at x=20).
+  hit = GetTargetAPZC(ScreenPoint(15, 75));
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75));
+  EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75));
+
+  // Hit an area on layers[1].
+  hit = GetTargetAPZC(ScreenPoint(25, 25));
+  EXPECT_EQ(apzc1, hit.get());
+  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
+  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
+
+  // Hit an area on layers[3].
+  hit = GetTargetAPZC(ScreenPoint(25, 75));
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75));
+  // and transformToGecko should reapply it
+  EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75));
+
+  // Hit an area on layers[3] that would be on the root if layers[2]
+  // weren't transformed.
+  hit = GetTargetAPZC(ScreenPoint(75, 75));
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75));
+  // and transformToGecko should reapply it
+  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75));
+
+  // Pan the root layer upward by 50 pixels.
+  // This causes layers[1] to scroll out of view, and an async transform
+  // of -50 to be set on the root layer.
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
+  // Since this paint request is in the queue to Gecko, transformToGecko will
+  // take it into account.
+  ApzcPanNoFling(apzcroot, mcc, 100, 50);
+
+  // Hit where layers[3] used to be. It should now hit the root.
+  hit = GetTargetAPZC(ScreenPoint(75, 75));
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
+  // and transformToGecko unapplies it and then reapplies it, because by the
+  // time the event being transformed reaches Gecko the new paint request will
+  // have been handled.
+  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
+
+  // Hit where layers[1] used to be and where layers[3] should now be.
+  hit = GetTargetAPZC(ScreenPoint(25, 25));
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc unapplies both layers[2]'s css transform and the root's
+  // async transform
+  EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25));
+  // transformToGecko reapplies both the css transform and the async transform
+  // because we have already issued a paint request with it.
+  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75));
+
+  // This second pan will move the APZC by another 50 pixels.
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+  ApzcPanNoFling(apzcroot, mcc, 100, 50);
+
+  // Hit where layers[3] used to be. It should now hit the root.
+  hit = GetTargetAPZC(ScreenPoint(75, 75));
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75));
+  // transformToGecko unapplies the full async transform of -100 pixels
+  EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75));
+
+  // Hit where layers[1] used to be. It should now hit the root.
+  hit = GetTargetAPZC(ScreenPoint(25, 25));
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25));
+  // transformToGecko unapplies the full async transform of -100 pixels
+  EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25));
+}
+
+TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
+  CreateComplexMultiLayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  /* The layer tree looks like this:
+
+                0
+        |----|--+--|----|
+        1    2     4    5
+             |         /|\
+             3        6 8 9
+                      |
+                      7
+
+     Layers 1,2 have the same APZC
+     Layers 4,6,8 have the same APZC
+     Layer 7 has an APZC
+     Layer 9 has an APZC
+  */
+
+  TestAsyncPanZoomController* nullAPZC = nullptr;
+  // Ensure all the scrollable layers have an APZC
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
+  EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
+  EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
+  EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
+  EXPECT_NE(nullAPZC, ApzcOf(layers[7]));
+  EXPECT_NE(nullAPZC, ApzcOf(layers[8]));
+  EXPECT_NE(nullAPZC, ApzcOf(layers[9]));
+  // Ensure those that scroll together have the same APZCs
+  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
+  EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6]));
+  EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
+  // Ensure those that don't scroll together have different APZCs
+  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
+  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
+  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
+  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
+  EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
+  EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
+  // Ensure the APZC parent chains are set up correctly
+  TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
+  TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
+  TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
+  TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
+  EXPECT_EQ(nullptr, layers1_2->GetParent());
+  EXPECT_EQ(nullptr, layers4_6_8->GetParent());
+  EXPECT_EQ(layers4_6_8, layer7->GetParent());
+  EXPECT_EQ(nullptr, layer9->GetParent());
+  // Ensure the hit-testing tree looks like the layer tree
+  RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
+  RefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
+  RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
+  RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
+  RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
+  RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
+  RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
+  RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
+  RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
+  RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
+  EXPECT_EQ(nullptr, node1->GetPrevSibling());
+  EXPECT_EQ(nullptr, node3->GetPrevSibling());
+  EXPECT_EQ(nullptr, node6->GetPrevSibling());
+  EXPECT_EQ(nullptr, node7->GetPrevSibling());
+  EXPECT_EQ(nullptr, node1->GetLastChild());
+  EXPECT_EQ(nullptr, node3->GetLastChild());
+  EXPECT_EQ(nullptr, node4->GetLastChild());
+  EXPECT_EQ(nullptr, node7->GetLastChild());
+  EXPECT_EQ(nullptr, node8->GetLastChild());
+  EXPECT_EQ(nullptr, node9->GetLastChild());
+
+  RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
+  EXPECT_EQ(ApzcOf(layers[1]), hit.get());
+  hit = GetTargetAPZC(ScreenPoint(275, 375));
+  EXPECT_EQ(ApzcOf(layers[9]), hit.get());
+  hit = GetTargetAPZC(ScreenPoint(250, 100));
+  EXPECT_EQ(ApzcOf(layers[7]), hit.get());
+}
+
+TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+
+  // The main purpose of this test is to verify that touch-start events (or anything
+  // that starts a new input block) don't ever get untransformed. This should always
+  // hold because the APZ code should flush repaints when we start a new input block
+  // and the transform to gecko space should be empty.
+
+  CreateSimpleScrollingLayer();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
+
+  // At this point, the following holds (all coordinates in screen pixels):
+  // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200)
+
+  MockFunction<void(std::string checkPointName)> check;
+
+  {
+    InSequence s;
+
+    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
+    EXPECT_CALL(check, Call("post-first-touch-start"));
+    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
+    EXPECT_CALL(check, Call("post-second-fling"));
+    EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
+    EXPECT_CALL(check, Call("post-second-touch-start"));
+  }
+
+  // This first pan will move the APZC by 50 pixels, and dispatch a paint request.
+  ApzcPanNoFling(apzcroot, mcc, 100, 50);
+
+  // Verify that a touch start doesn't get untransformed
+  ScreenIntPoint touchPoint(50, 50);
+  MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
+  mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0));
+
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
+  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
+  check.Call("post-first-touch-start");
+
+  // Send a touchend to clear state
+  mti.mType = MultiTouchInput::MULTITOUCH_END;
+  manager->ReceiveInputEvent(mti, nullptr, nullptr);
+
+  mcc->AdvanceByMillis(1000);
+
+  // Now do two pans. The first of these will dispatch a repaint request, as above.
+  // The second will get stuck in the paint throttler because the first one doesn't
+  // get marked as "completed", so this will result in a non-empty LD transform.
+  // (Note that any outstanding repaint requests from the first half of this test
+  // don't impact this half because we advance the time by 1 second, which will trigger
+  // the max-wait-exceeded codepath in the paint throttler).
+  ApzcPanNoFling(apzcroot, mcc, 100, 50);
+  check.Call("post-second-fling");
+  ApzcPanNoFling(apzcroot, mcc, 100, 50);
+
+  // Ensure that a touch start again doesn't get untransformed by flushing
+  // a repaint
+  mti.mType = MultiTouchInput::MULTITOUCH_START;
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
+  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
+  check.Call("post-second-touch-start");
+
+  mti.mType = MultiTouchInput::MULTITOUCH_END;
+  EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
+  EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
+}
+
+TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) {
+  // The purpose of this test is to ensure that wheel events trigger a repaint
+  // flush as per bug 1166871, and that the wheel event untransform is a no-op.
+
+  CreateSimpleScrollingLayer();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  TestAsyncPanZoomController* apzcroot = ApzcOf(root);
+
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3));
+  ScreenPoint origin(100, 50);
+  for (int i = 0; i < 3; i++) {
+    ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
+      ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
+      origin, 0, 10);
+    EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
+    EXPECT_EQ(origin, swi.mOrigin);
+
+    AsyncTransform viewTransform;
+    ParentLayerPoint point;
+    apzcroot->SampleContentTransformForFrame(&viewTransform, point);
+    EXPECT_EQ(0, point.x);
+    EXPECT_EQ((i + 1) * 10, point.y);
+    EXPECT_EQ(0, viewTransform.mTranslation.x);
+    EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y);
+
+    mcc->AdvanceByMillis(5);
+  }
+}
+
+TEST_F(APZHitTestingTester, Bug1148350) {
+  CreateBug1148350LayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  MockFunction<void(std::string checkPointName)> check;
+  {
+    InSequence s;
+    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped without transform"));
+    EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(100, 100), 0, ApzcOf(layers[1])->GetGuid())).Times(1);
+    EXPECT_CALL(check, Call("Tapped with interleaved transform"));
+  }
+
+  Tap(manager, 100, 100, mcc, TimeDuration::FromMilliseconds(100));
+  mcc->RunThroughDelayedTasks();
+  check.Call("Tapped without transform");
+
+  uint64_t blockId;
+  TouchDown(manager, 100, 100, mcc->Time(), &blockId);
+  if (gfxPrefs::TouchActionEnabled()) {
+    SetDefaultAllowedTouchBehavior(manager, blockId);
+  }
+  mcc->AdvanceByMillis(100);
+
+  layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150)));
+  layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0));
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  TouchUp(manager, 100, 100, mcc->Time());
+  mcc->RunThroughDelayedTasks();
+  check.Call("Tapped with interleaved transform");
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestOverscrollHandoff.cpp
@@ -0,0 +1,430 @@
+/* -*- 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 "APZCTreeManagerTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+class APZOverscrollHandoffTester : public APZCTreeManagerTester {
+protected:
+  UniquePtr<ScopedLayerTreeRegistration> registration;
+  TestAsyncPanZoomController* rootApzc;
+
+  void CreateOverscrollHandoffLayerTree1() {
+    const char* layerTreeSyntax = "c(t)";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 50, 100, 50))
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
+    SetScrollHandoff(layers[1], root);
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateOverscrollHandoffLayerTree2() {
+    const char* layerTreeSyntax = "c(c(t))";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 0, 100, 100)),
+      nsIntRegion(IntRect(0, 50, 100, 50))
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
+    SetScrollHandoff(layers[1], root);
+    SetScrollHandoff(layers[2], layers[1]);
+    // No ScopedLayerTreeRegistration as that just needs to be done once per test
+    // and this is the second layer tree for a particular test.
+    MOZ_ASSERT(registration);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+  }
+
+  void CreateOverscrollHandoffLayerTree3() {
+    const char* layerTreeSyntax = "c(c(t)c(t))";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),  // root
+      nsIntRegion(IntRect(0, 0, 100, 50)),   // scrolling parent 1
+      nsIntRegion(IntRect(0, 0, 100, 50)),   // scrolling child 1
+      nsIntRegion(IntRect(0, 50, 100, 50)),  // scrolling parent 2
+      nsIntRegion(IntRect(0, 50, 100, 50))   // scrolling child 2
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100));
+    SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
+    SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 50, 100, 100));
+    SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100));
+    SetScrollHandoff(layers[2], layers[1]);
+    SetScrollHandoff(layers[4], layers[3]);
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  }
+
+  void CreateScrollgrabLayerTree(bool makeParentScrollable = true) {
+    const char* layerTreeSyntax = "c(t)";
+    nsIntRegion layerVisibleRegion[] = {
+      nsIntRegion(IntRect(0, 0, 100, 100)),  // scroll-grabbing parent
+      nsIntRegion(IntRect(0, 20, 100, 80))   // child
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
+    float parentHeight = makeParentScrollable ? 120 : 100;
+    SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight));
+    SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
+    SetScrollHandoff(layers[1], root);
+    registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
+    manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+    rootApzc = ApzcOf(root);
+    rootApzc->GetFrameMetrics().SetHasScrollgrab(true);
+  }
+
+  void TestFlingAcceleration() {
+    // Jack up the fling acceleration multiplier so we can easily determine
+    // whether acceleration occured.
+    const float kAcceleration = 100.0f;
+    SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration);
+
+    RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
+
+    // Pan once, enough to fully scroll the scrollgrab parent and then scroll
+    // and fling the child.
+    Pan(manager, mcc, 70, 40);
+
+    // Give the fling animation a chance to start.
+    SampleAnimationsOnce();
+
+    float childVelocityAfterFling1 = childApzc->GetVelocityVector().y;
+
+    // Pan again.
+    Pan(manager, mcc, 70, 40);
+
+    // Give the fling animation a chance to start.
+    // This time it should be accelerated.
+    SampleAnimationsOnce();
+
+    float childVelocityAfterFling2 = childApzc->GetVelocityVector().y;
+
+    // We should have accelerated once.
+    // The division by 2 is to account for friction.
+    EXPECT_GT(childVelocityAfterFling2,
+              childVelocityAfterFling1 * kAcceleration / 2);
+
+    // We should not have accelerated twice.
+    // The division by 4 is to account for friction.
+    EXPECT_LE(childVelocityAfterFling2,
+              childVelocityAfterFling1 * kAcceleration * kAcceleration / 4);
+  }
+};
+
+// Here we test that if the processing of a touch block is deferred while we
+// wait for content to send a prevent-default message, overscroll is still
+// handed off correctly when the block is processed.
+TEST_F(APZOverscrollHandoffTester, DeferredInputEventProcessing) {
+  // Set up the APZC tree.
+  CreateOverscrollHandoffLayerTree1();
+
+  TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
+
+  // Enable touch-listeners so that we can separate the queueing of input
+  // events from them being processed.
+  childApzc->SetWaitForMainThread();
+
+  // Queue input events for a pan.
+  uint64_t blockId = 0;
+  ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
+
+  // Allow the pan to be processed.
+  childApzc->ContentReceivedInputBlock(blockId, false);
+  childApzc->ConfirmTarget(blockId);
+
+  // Make sure overscroll was handed off correctly.
+  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
+}
+
+// Here we test that if the layer structure changes in between two input
+// blocks being queued, and the first block is only processed after the second
+// one has been queued, overscroll handoff for the first block follows
+// the original layer structure while overscroll handoff for the second block
+// follows the new layer structure.
+TEST_F(APZOverscrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
+  // Set up an initial APZC tree.
+  CreateOverscrollHandoffLayerTree1();
+
+  TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
+
+  // Enable touch-listeners so that we can separate the queueing of input
+  // events from them being processed.
+  childApzc->SetWaitForMainThread();
+
+  // Queue input events for a pan.
+  uint64_t blockId = 0;
+  ApzcPanNoFling(childApzc, mcc, 90, 30, &blockId);
+
+  // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
+  // between the child and the root.
+  CreateOverscrollHandoffLayerTree2();
+  RefPtr<Layer> middle = layers[1];
+  childApzc->SetWaitForMainThread();
+  TestAsyncPanZoomController* middleApzc = ApzcOf(middle);
+
+  // Queue input events for another pan.
+  uint64_t secondBlockId = 0;
+  ApzcPanNoFling(childApzc, mcc, 30, 90, &secondBlockId);
+
+  // Allow the first pan to be processed.
+  childApzc->ContentReceivedInputBlock(blockId, false);
+  childApzc->ConfirmTarget(blockId);
+
+  // Make sure things have scrolled according to the handoff chain in
+  // place at the time the touch-start of the first pan was queued.
+  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y);
+
+  // Allow the second pan to be processed.
+  childApzc->ContentReceivedInputBlock(secondBlockId, false);
+  childApzc->ConfirmTarget(secondBlockId);
+
+  // Make sure things have scrolled according to the handoff chain in
+  // place at the time the touch-start of the second pan was queued.
+  EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y);
+}
+
+// Test that putting a second finger down on an APZC while a down-chain APZC
+// is overscrolled doesn't result in being stuck in overscroll.
+TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1073250) {
+  // Enable overscrolling.
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  CreateOverscrollHandoffLayerTree1();
+
+  TestAsyncPanZoomController* child = ApzcOf(layers[1]);
+
+  // Pan, causing the parent APZC to overscroll.
+  Pan(manager, mcc, 10, 40, true /* keep finger down */);
+  EXPECT_FALSE(child->IsOverscrolled());
+  EXPECT_TRUE(rootApzc->IsOverscrolled());
+
+  // Put a second finger down.
+  MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
+  secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
+  secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
+  manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
+
+  // Release the fingers.
+  MultiTouchInput fingersUp = secondFingerDown;
+  fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
+  manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
+
+  // Allow any animations to run their course.
+  child->AdvanceAnimationsUntilEnd();
+  rootApzc->AdvanceAnimationsUntilEnd();
+
+  // Make sure nothing is overscrolled.
+  EXPECT_FALSE(child->IsOverscrolled());
+  EXPECT_FALSE(rootApzc->IsOverscrolled());
+}
+
+// This is almost exactly like StuckInOverscroll_Bug1073250, except the
+// APZC receiving the input events for the first touch block is the child
+// (and thus not the same APZC that overscrolls, which is the parent).
+TEST_F(APZOverscrollHandoffTester, StuckInOverscroll_Bug1231228) {
+  // Enable overscrolling.
+  SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
+
+  CreateOverscrollHandoffLayerTree1();
+
+  TestAsyncPanZoomController* child = ApzcOf(layers[1]);
+
+  // Pan, causing the parent APZC to overscroll.
+  Pan(manager, mcc, 60, 90, true /* keep finger down */);
+  EXPECT_FALSE(child->IsOverscrolled());
+  EXPECT_TRUE(rootApzc->IsOverscrolled());
+
+  // Put a second finger down.
+  MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
+  // Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
+  secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
+  secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
+  manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
+
+  // Release the fingers.
+  MultiTouchInput fingersUp = secondFingerDown;
+  fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
+  manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
+
+  // Allow any animations to run their course.
+  child->AdvanceAnimationsUntilEnd();
+  rootApzc->AdvanceAnimationsUntilEnd();
+
+  // Make sure nothing is overscrolled.
+  EXPECT_FALSE(child->IsOverscrolled());
+  EXPECT_FALSE(rootApzc->IsOverscrolled());
+}
+
+// Test that flinging in a direction where one component of the fling goes into
+// overscroll but the other doesn't, results in just the one component being
+// handed off to the parent, while the original APZC continues flinging in the
+// other direction.
+TEST_F(APZOverscrollHandoffTester, PartialFlingHandoff) {
+  CreateOverscrollHandoffLayerTree1();
+
+  // Fling up and to the left. The child APZC has room to scroll up, but not
+  // to the left, so the horizontal component of the fling should be handed
+  // off to the parent APZC.
+  Pan(manager, mcc, ScreenPoint(90, 90), ScreenPoint(55, 55));
+
+  RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root);
+  RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]);
+
+  // Advance the child's fling animation once to give the partial handoff
+  // a chance to occur.
+  mcc->AdvanceByMillis(10);
+  child->AdvanceAnimations(mcc->Time());
+
+  // Assert that partial handoff has occurred.
+  child->AssertStateIsFling();
+  parent->AssertStateIsFling();
+}
+
+// Here we test that if two flings are happening simultaneously, overscroll
+// is handed off correctly for each.
+TEST_F(APZOverscrollHandoffTester, SimultaneousFlings) {
+  // Set up an initial APZC tree.
+  CreateOverscrollHandoffLayerTree3();
+
+  RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]);
+  RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]);
+  RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]);
+  RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]);
+
+  // Pan on the lower child.
+  Pan(child2, mcc, 45, 5);
+
+  // Pan on the upper child.
+  Pan(child1, mcc, 95, 55);
+
+  // Check that child1 and child2 are in a FLING state.
+  child1->AssertStateIsFling();
+  child2->AssertStateIsFling();
+
+  // Advance the animations on child1 and child2 until their end.
+  child1->AdvanceAnimationsUntilEnd();
+  child2->AdvanceAnimationsUntilEnd();
+
+  // Check that the flings have been handed off to the parents.
+  child1->AssertStateIsReset();
+  parent1->AssertStateIsFling();
+  child2->AssertStateIsReset();
+  parent2->AssertStateIsFling();
+}
+
+TEST_F(APZOverscrollHandoffTester, Scrollgrab) {
+  // Set up the layer tree
+  CreateScrollgrabLayerTree();
+
+  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
+
+  // Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
+  // and leave some more (another 15 px) for the child.
+  Pan(childApzc, mcc, 80, 45);
+
+  // Check that the parent and child have scrolled as much as we expect.
+  EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y);
+}
+
+TEST_F(APZOverscrollHandoffTester, ScrollgrabFling) {
+  // Set up the layer tree
+  CreateScrollgrabLayerTree();
+
+  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
+
+  // Pan on the child, not enough to fully scroll the scrollgrab parent.
+  Pan(childApzc, mcc, 80, 70);
+
+  // Check that it is the scrollgrab parent that's in a fling, not the child.
+  rootApzc->AssertStateIsFling();
+  childApzc->AssertStateIsReset();
+}
+
+TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration1) {
+  CreateScrollgrabLayerTree(true /* make parent scrollable */);
+  TestFlingAcceleration();
+}
+
+TEST_F(APZOverscrollHandoffTester, ScrollgrabFlingAcceleration2) {
+  CreateScrollgrabLayerTree(false /* do not make parent scrollable */);
+  TestFlingAcceleration();
+}
+
+TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Pan) {
+  SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
+
+  CreateOverscrollHandoffLayerTree1();
+
+  RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
+  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
+
+  // Pan on the child, enough to scroll it to its end and have scroll
+  // left to hand off. Since immediate handoff is disallowed, we expect
+  // the leftover scroll not to be handed off.
+  Pan(childApzc, mcc, 60, 5);
+
+  // Verify that the parent has not scrolled.
+  EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
+  EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
+
+  // Pan again on the child. This time, since the child was scrolled to
+  // its end when the gesture began, we expect the scroll to be handed off.
+  Pan(childApzc, mcc, 60, 50);
+
+  // Verify that the parent scrolled.
+  EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y);
+}
+
+TEST_F(APZOverscrollHandoffTester, ImmediateHandoffDisallowed_Fling) {
+  SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
+
+  CreateOverscrollHandoffLayerTree1();
+
+  RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
+  RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
+
+  // Pan on the child, enough to get very close to the end, so that the
+  // subsequent fling reaches the end and has leftover velocity to hand off.
+  Pan(childApzc, mcc, 60, 12);
+
+  // Allow the fling to run its course.
+  childApzc->AdvanceAnimationsUntilEnd();
+  parentApzc->AdvanceAnimationsUntilEnd();
+
+  // Verify that the parent has not scrolled.
+  // The first comparison needs to be an ASSERT_NEAR because the fling
+  // computations are such that the final scroll position can be within
+  // COORDINATE_EPSILON of the end rather than right at the end.
+  ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON);
+  EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
+
+  // Pan again on the child. This time, since the child was scrolled to
+  // its end when the gesture began, we expect the scroll to be handed off.
+  Pan(childApzc, mcc, 60, 50);
+
+  // Allow the fling to run its course. The fling should also be handed off.
+  childApzc->AdvanceAnimationsUntilEnd();
+  parentApzc->AdvanceAnimationsUntilEnd();
+
+  // Verify that the parent scrolled from the fling.
+  EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestPanning.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "APZCBasicTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+class APZCPanningTester : public APZCBasicTester {
+protected:
+  void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
+  {
+    if (aShouldTriggerScroll) {
+      // One repaint request for each pan.
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
+    } else {
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+    }
+
+    int touchStart = 50;
+    int touchEnd = 10;
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+
+    nsTArray<uint32_t> allowedTouchBehaviors;
+    allowedTouchBehaviors.AppendElement(aBehavior);
+
+    // Pan down
+    PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors);
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+
+    if (aShouldTriggerScroll) {
+      EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut);
+      EXPECT_NE(AsyncTransform(), viewTransformOut);
+    } else {
+      EXPECT_EQ(ParentLayerPoint(), pointOut);
+      EXPECT_EQ(AsyncTransform(), viewTransformOut);
+    }
+
+    // Clear the fling from the previous pan, or stopping it will
+    // consume the next touchstart
+    apzc->CancelAnimation();
+
+    // Pan back
+    PanAndCheckStatus(apzc, mcc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors);
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+
+    EXPECT_EQ(ParentLayerPoint(), pointOut);
+    EXPECT_EQ(AsyncTransform(), viewTransformOut);
+  }
+
+  void DoPanWithPreventDefaultTest()
+  {
+    MakeApzcWaitForMainThread();
+
+    int touchStart = 50;
+    int touchEnd = 10;
+    ParentLayerPoint pointOut;
+    AsyncTransform viewTransformOut;
+    uint64_t blockId = 0;
+
+    // Pan down
+    nsTArray<uint32_t> allowedTouchBehaviors;
+    allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
+    PanAndCheckStatus(apzc, mcc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId);
+
+    // Send the signal that content has handled and preventDefaulted the touch
+    // events. This flushes the event queue.
+    apzc->ContentReceivedInputBlock(blockId, true);
+
+    apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
+    EXPECT_EQ(ParentLayerPoint(), pointOut);
+    EXPECT_EQ(AsyncTransform(), viewTransformOut);
+
+    apzc->AssertStateIsReset();
+  }
+};
+
+TEST_F(APZCPanningTester, Pan) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE);
+}
+
+// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top
+// to bottom and back - from bottom to top.
+// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical
+// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this
+// behavior.
+// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could
+// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the
+// events.
+TEST_F(APZCPanningTester, PanWithTouchActionAuto) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
+                      | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
+}
+
+TEST_F(APZCPanningTester, PanWithTouchActionNone) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoPanTest(false, false, 0);
+}
+
+TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
+}
+
+TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
+}
+
+TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  DoPanWithPreventDefaultTest();
+}
+
+TEST_F(APZCPanningTester, PanWithPreventDefault) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoPanWithPreventDefaultTest();
+}
+
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestPinching.cpp
@@ -0,0 +1,175 @@
+/* -*- 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 "APZCBasicTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+class APZCPinchTester : public APZCBasicTester {
+public:
+  explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
+    : APZCBasicTester(aGestureBehavior)
+  {
+  }
+
+protected:
+  FrameMetrics GetPinchableFrameMetrics()
+  {
+    FrameMetrics fm;
+    fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
+    fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
+    fm.SetScrollOffset(CSSPoint(300, 300));
+    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
+    // APZC only allows zooming on the root scrollable frame.
+    fm.SetIsRootContent(true);
+    // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
+    return fm;
+  }
+
+  void DoPinchTest(bool aShouldTriggerPinch,
+                   nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
+  {
+    apzc->SetFrameMetrics(GetPinchableFrameMetrics());
+    MakeApzcZoomable();
+
+    if (aShouldTriggerPinch) {
+      // One repaint request for each gesture.
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
+    } else {
+      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
+    }
+
+    int touchInputId = 0;
+    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
+      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
+    } else {
+      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
+    }
+
+    FrameMetrics fm = apzc->GetFrameMetrics();
+
+    if (aShouldTriggerPinch) {
+      // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
+      EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale);
+      EXPECT_EQ(305, fm.GetScrollOffset().x);
+      EXPECT_EQ(310, fm.GetScrollOffset().y);
+    } else {
+      // The frame metrics should stay the same since touch-action:none makes
+      // apzc ignore pinch gestures.
+      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
+      EXPECT_EQ(300, fm.GetScrollOffset().x);
+      EXPECT_EQ(300, fm.GetScrollOffset().y);
+    }
+
+    // part 2 of the test, move to the top-right corner of the page and pinch and
+    // make sure we stay in the correct spot
+    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
+    fm.SetScrollOffset(CSSPoint(930, 5));
+    apzc->SetFrameMetrics(fm);
+    // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
+
+    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
+      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
+    } else {
+      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch);
+    }
+
+    fm = apzc->GetFrameMetrics();
+
+    if (aShouldTriggerPinch) {
+      // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
+      EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale);
+      EXPECT_EQ(880, fm.GetScrollOffset().x);
+      EXPECT_EQ(0, fm.GetScrollOffset().y);
+    } else {
+      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
+      EXPECT_EQ(930, fm.GetScrollOffset().x);
+      EXPECT_EQ(5, fm.GetScrollOffset().y);
+    }
+  }
+};
+
+class APZCPinchGestureDetectorTester : public APZCPinchTester {
+public:
+  APZCPinchGestureDetectorTester()
+    : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
+  {
+  }
+};
+
+TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoPinchTest(true);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
+  DoPinchTest(true);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
+                                   mozilla::layers::AllowedTouchBehavior::NONE };
+  DoPinchTest(false, &behaviors);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  nsTArray<uint32_t> behaviors;
+  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
+  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
+  DoPinchTest(true, &behaviors);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
+  SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
+  nsTArray<uint32_t> behaviors;
+  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
+  behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
+  DoPinchTest(false, &behaviors);
+}
+
+TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
+  FrameMetrics originalMetrics = GetPinchableFrameMetrics();
+  apzc->SetFrameMetrics(originalMetrics);
+
+  MakeApzcWaitForMainThread();
+  MakeApzcZoomable();
+
+  int touchInputId = 0;
+  uint64_t blockId = 0;
+  PinchWithTouchInput(apzc, 250, 300, 1.25, touchInputId, nullptr, nullptr, &blockId);
+
+  // Send the prevent-default notification for the touch block
+  apzc->ContentReceivedInputBlock(blockId, true);
+
+  // verify the metrics didn't change (i.e. the pinch was ignored)
+  FrameMetrics fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
+  EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
+  EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
+
+  apzc->AssertStateIsReset();
+}
+
+TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) {
+  // set up APZ
+  apzc->SetFrameMetrics(GetPinchableFrameMetrics());
+  MakeApzcUnzoomable();
+
+  nsEventStatus statuses[3];  // scalebegin, scale, scaleend
+  PinchWithPinchInput(apzc, 250, 350, 200, 300, 10, &statuses);
+
+  FrameMetrics fm = apzc->GetFrameMetrics();
+
+  // It starts from (300, 300), then moves the focus point from (250, 350) to
+  // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which
+  // causes the scroll offset to change by half of that (25, 25) pixels.
+  EXPECT_EQ(325, fm.GetScrollOffset().x);
+  EXPECT_EQ(325, fm.GetScrollOffset().y);
+  EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp
@@ -0,0 +1,112 @@
+/* -*- 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 "APZCTreeManagerTester.h"
+#include "APZTestCommon.h"
+#include "InputUtils.h"
+
+TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) {
+  CreateSimpleMultiLayerTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+
+  // both layers have the same scrollId
+  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
+  SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+
+  TestAsyncPanZoomController* nullAPZC = nullptr;
+  // so they should have the same APZC
+  EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
+  EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
+  EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
+  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
+
+  // Change the scrollId of layers[1], and verify the APZC changes
+  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2]));
+
+  // Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
+  // APZC for both again
+  SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
+}
+
+TEST_F(APZCTreeManagerTester, Bug1068268) {
+  CreatePotentiallyLeakingTree();
+  ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
+
+  manager->UpdateHitTestingTree(nullptr, root, false, 0, 0);
+  RefPtr<HitTestingTreeNode> root