Merge b2g-inbound to m-c
authorWes Kocher <wkocher@mozilla.com>
Mon, 04 Nov 2013 21:07:33 -0800
changeset 153519 789ad4b1f49e3d9e60dab8b88f04e8d5b9e374a6
parent 153504 b0bce439c6e2e64225d7add4954e2983bd0d57ab (current diff)
parent 153518 69a8799655a4c6d5a4e8fdcb6b3f5f8bf476fc40 (diff)
child 153520 5ba522ec7f89657b8d4f4c66c9582a69ee85c3dc
push id35816
push userkwierso@gmail.com
push dateTue, 05 Nov 2013 05:22:53 +0000
treeherdermozilla-inbound@442b47e9fb80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "00ceae21c52602059b7614b661bc39a3c73c84de", 
+    "revision": "e9d3946c6e4c26c60f67b8efac40e14785b634d3", 
     "repo_path": "/integration/gaia-central"
 }
--- a/content/media/MediaRecorder.cpp
+++ b/content/media/MediaRecorder.cpp
@@ -56,18 +56,20 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder,
  *    Switch from Extract stage to Destroy stage by calling Session::Stop.
  *    Release session resource and remove associated streams from MSG.
  *
  * Lifetime of a Session object.
  * 1) MediaRecorder creates a Session in MediaRecorder::Start function.
  * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
  *    _and_ all encoded media data been passed to OnDataAvailable handler.
  */
-class MediaRecorder::Session
+class MediaRecorder::Session: public nsIObserver
 {
+  NS_DECL_THREADSAFE_ISUPPORTS
+
   // Main thread task.
   // Create a blob event and send back to client.
   class PushBlobRunnable : public nsRunnable
   {
   public:
     PushBlobRunnable(Session* aSession)
       : mSession(aSession)
     { }
@@ -109,115 +111,101 @@ class MediaRecorder::Session
     Session *mSession;
   };
 
   // Main thread task.
   // To delete RecordingSession object.
   class DestroyRunnable : public nsRunnable
   {
   public:
-    DestroyRunnable(Session *aSession)
+    DestroyRunnable(const already_AddRefed<Session> &aSession)
       : mSession(aSession) {}
 
     NS_IMETHODIMP Run()
     {
       MOZ_ASSERT(NS_IsMainThread() && mSession.get());
       MediaRecorder *recorder = mSession->mRecorder;
 
-      // If MediaRecoder is not in Inactive mode, call MediaRecoder::Stop
-      // and dispatch DestroyRunnable again.
+      // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
+      // Read Thread will be terminate soon.
+      // We need to switch MediaRecorder to "Stop" state first to make sure
+      // MediaRecorder is not associated with this Session anymore, then, it's
+      // safe to delete this Session.
       if (recorder->mState != RecordingState::Inactive) {
         ErrorResult result;
         recorder->Stop(result);
         NS_DispatchToMainThread(new DestroyRunnable(mSession.forget()));
+
         return NS_OK;
       }
 
       // Dispatch stop event and clear MIME type.
       recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
       recorder->SetMimeType(NS_LITERAL_STRING(""));
 
-      // Delete session object.
-      mSession = nullptr;
-
       return NS_OK;
     }
 
   private:
-    nsAutoPtr<Session> mSession;
+    // Call mSession::Release automatically while DestroyRunnable be destroy.
+    nsRefPtr<Session> mSession;
   };
 
   friend class PushBlobRunnable;
   friend class ExtractRunnable;
   friend class DestroyRunnable;
 
 public:
   Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
     : mRecorder(aRecorder),
       mTimeSlice(aTimeSlice)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    AddRef();
     mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
   }
 
   // Only DestroyRunnable is allowed to delete Session object.
-  ~Session()
+  virtual ~Session()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (mInputPort.get()) {
-      mInputPort->Destroy();
-    }
-
-    if (mTrackUnionStream.get()) {
-      mTrackUnionStream->Destroy();
-    }
+    CleanupStreams();
   }
 
   void Start()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     SetupStreams();
 
     // Create a thread to read encode media data from MediaEncoder.
     if (!mReadThread) {
       nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
       if (NS_FAILED(rv)) {
-        if (mInputPort.get()) {
-          mInputPort->Destroy();
-        }
-        if (mTrackUnionStream.get()) {
-          mTrackUnionStream->Destroy();
-        }
+        CleanupStreams();
         mRecorder->NotifyError(rv);
         return;
       }
     }
 
+    // In case source media stream does not notify track end, recieve
+    // shutdown notification and stop Read Thread.
+    nsContentUtils::RegisterShutdownObserver(this);
+
     mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
   }
 
   void Stop()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    // Shutdown mEncoder to stop Session::Extract
-    if (mInputPort.get())
-    {
-      mInputPort->Destroy();
-      mInputPort = nullptr;
-    }
-
-    if (mTrackUnionStream.get())
-    {
-      mTrackUnionStream->Destroy();
-      mTrackUnionStream = nullptr;
-    }
+    CleanupStreams();
+    nsContentUtils::UnregisterShutdownObserver(this);
   }
 
   void Pause()
   {
     MOZ_ASSERT(NS_IsMainThread() && mTrackUnionStream);
 
     mTrackUnionStream->ChangeExplicitBlockerCount(-1);
   }
@@ -228,16 +216,17 @@ public:
 
     mTrackUnionStream->ChangeExplicitBlockerCount(1);
   }
 
   already_AddRefed<nsIDOMBlob> GetEncodedData()
   {
     nsString mimeType;
     mRecorder->GetMimeType(mimeType);
+
     return mEncodedBufferCache->ExtractBlob(mimeType);
   }
 
 private:
 
   // Pull encoded meida data from MediaEncoder and put into EncodedBufferCache.
   // Destroy this session object in the end of this function.
   void Extract()
@@ -268,46 +257,74 @@ private:
         }
       }
     } while (!mEncoder->IsShutdown());
 
     // Flush out remainding encoded data.
     NS_DispatchToMainThread(new PushBlobRunnable(this));
 
     // Destroy this session object in main thread.
-    NS_DispatchToMainThread(new DestroyRunnable(this));
+    NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(this)));
   }
 
   // Bind media source with MediaEncoder to receive raw media data.
   void SetupStreams()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    // Create a Track Union Stream
     MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph();
     mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
 
     mTrackUnionStream->SetAutofinish(true);
 
+    // Bind this Track Union Stream with Source Media
     mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
 
-    // Allocate encoder and bind with union stream.
+    // Allocate encoder and bind with the Track Union Stream.
     mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""));
     MOZ_ASSERT(mEncoder, "CreateEncoder failed");
 
     if (mEncoder) {
       mTrackUnionStream->AddListener(mEncoder);
     }
   }
 
+  void CleanupStreams()
+  {
+    if (mInputPort.get()) {
+      mInputPort->Destroy();
+      mInputPort = nullptr;
+    }
+
+    if (mTrackUnionStream.get()) {
+      mTrackUnionStream->Destroy();
+      mTrackUnionStream = nullptr;
+    }
+  }
+
+  NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+      // Force stop Session to terminate Read Thread.
+      Stop();
+    }
+
+    return NS_OK;
+  }
+
 private:
   // Hold a reference to MediaRecoder to make sure MediaRecoder be
   // destroyed after all session object dead.
   nsRefPtr<MediaRecorder> mRecorder;
 
+  // Receive track data from source and dispatch to Encoder.
   // Pause/ Resume controller.
   nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
   nsRefPtr<MediaInputPort> mInputPort;
 
   // Runnable thread for read data from MediaEncode.
   nsCOMPtr<nsIThread> mReadThread;
   // MediaEncoder pipeline.
   nsRefPtr<MediaEncoder> mEncoder;
@@ -315,16 +332,18 @@ private:
   nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
   // The interval of passing encoded data from EncodedBufferCache to onDataAvailable
   // handler. "mTimeSlice < 0" means Session object does not push encoded data to
   // onDataAvailable, instead, it passive wait the client side pull encoded data
   // by calling requestData API.
   const int32_t mTimeSlice;
 };
 
+NS_IMPL_ISUPPORTS1(MediaRecorder::Session, nsIObserver)
+
 MediaRecorder::~MediaRecorder()
 {
   MOZ_ASSERT(mSession == nullptr);
 }
 
 void
 MediaRecorder::Init(nsPIDOMWindow* aOwnerWindow)
 {
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -2149,26 +2149,27 @@ this.DOMApplicationRegistry = {
   },
 
   // This function is called after we called the onsuccess callback on the
   // content side. This let the webpage the opportunity to set event handlers
   // on the app before we start firing progress events.
   queuedDownload: {},
   queuedPackageDownload: {},
 
-  onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
+onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
+                                                  aDontNeedNetwork) {
     // If we are offline, register to run when we'll be online.
-    if (Services.io.offline) {
+    if ((Services.io.offline) && !aDontNeedNetwork) {
       let onlineWrapper = {
         observe: function(aSubject, aTopic, aData) {
           Services.obs.removeObserver(onlineWrapper,
                                       "network:offline-status-changed");
           DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
         }
-      }
+      };
       Services.obs.addObserver(onlineWrapper,
                                "network:offline-status-changed", false);
       return;
     }
 
     let cacheDownload = this.queuedDownload[aManifestURL];
     if (cacheDownload) {
       this.startOfflineCacheDownload(cacheDownload.manifest,
@@ -2363,39 +2364,40 @@ this.DOMApplicationRegistry = {
     }).bind(this));
 
     if (!aData.isPackage) {
       this.updateAppHandlers(null, app.manifest, app);
       if (aInstallSuccessCallback) {
         aInstallSuccessCallback(app.manifest);
       }
     }
-
+    let dontNeedNetwork = false;
     if (manifest.package_path) {
       // If it is a local app then it must been installed from a local file
       // instead of web.
       let origPath = jsonManifest.package_path;
       if (aData.app.localInstallPath) {
+        dontNeedNetwork = true;
         jsonManifest.package_path = "file://" + aData.app.localInstallPath;
       }
       // origin for install apps is meaningless here, since it's app:// and this
       // can't be used to resolve package paths.
       manifest = new ManifestHelper(jsonManifest, app.manifestURL);
 
       this.queuedPackageDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         callback: aInstallSuccessCallback
       };
     }
 
     if (aData.forceSuccessAck) {
       // If it's a local install, there's no content process so just
       // ack the install.
-      this.onInstallSuccessAck(app.manifestURL);
+      this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork);
     }
   },
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
 
     while (this.getManifestURLByLocalId(id)) {
       id++;
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -9,16 +9,20 @@
 #include "mozilla/Hal.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/FMRadioBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PFMRadioChild.h"
 #include "mozilla/dom/FMRadioService.h"
 #include "DOMRequest.h"
+#include "nsDOMClassInfo.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAudioManager.h"
 
 #undef LOG
 #define LOG(args...) FM_LOG("FMRadio", args)
 
 // The pref indicates if the device has an internal antenna.
 // If the pref is true, the antanna will be always available.
 #define DOM_FM_ANTENNA_INTERNAL_PREF "dom.fmradio.antenna.internal"
 
@@ -105,27 +109,53 @@ FMRadio::Init(nsPIDOMWindow *aWindow)
   mHasInternalAntenna = Preferences::GetBool(DOM_FM_ANTENNA_INTERNAL_PREF,
                                              /* default = */ false);
   if (mHasInternalAntenna) {
     LOG("We have an internal antenna.");
   } else {
     mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES);
     RegisterSwitchObserver(SWITCH_HEADPHONES, this);
   }
+
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
+                                 /* useCapture = */ true,
+                                 /* wantsUntrusted = */ false);
+
+  mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
+  if (!mAudioChannelAgent) {
+    return;
+  }
+
+  mAudioChannelAgent->InitWithWeakCallback(nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_CONTENT,
+                                           this);
+
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  if (docshell) {
+    bool isActive = false;
+    docshell->GetIsActive(&isActive);
+    mAudioChannelAgent->SetVisibilityState(isActive);
+  }
 }
 
 void
 FMRadio::Shutdown()
 {
   IFMRadioService::Singleton()->RemoveObserver(this);
 
   if (!mHasInternalAntenna) {
     UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
   }
 
+  nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
+  NS_ENSURE_TRUE_VOID(target);
+  target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
+                                    /* useCapture = */ true);
+
   mIsShutdown = true;
 }
 
 JSObject*
 FMRadio::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return FMRadioBinding::Wrap(aCx, aScope, this);
 }
@@ -146,18 +176,24 @@ void
 FMRadio::Notify(const FMRadioEventType& aType)
 {
   switch (aType) {
     case FrequencyChanged:
       DispatchTrustedEvent(NS_LITERAL_STRING("frequencychange"));
       break;
     case EnabledChanged:
       if (Enabled()) {
+        int32_t playingState = 0;
+        mAudioChannelAgent->StartPlaying(&playingState);
+        SetCanPlay(playingState == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
+
         DispatchTrustedEvent(NS_LITERAL_STRING("enabled"));
       } else {
+        mAudioChannelAgent->StopPlaying();
+
         DispatchTrustedEvent(NS_LITERAL_STRING("disabled"));
       }
       break;
     default:
       MOZ_CRASH();
   }
 }
 
@@ -279,17 +315,52 @@ FMRadio::CancelSeek()
   }
 
   nsRefPtr<FMRadioRequest> r = new FMRadioRequest(win, this);
   IFMRadioService::Singleton()->CancelSeek(r);
 
   return r.forget();
 }
 
+NS_IMETHODIMP
+FMRadio::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+
+  if (!type.EqualsLiteral("visibilitychange")) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
+  NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+
+  mAudioChannelAgent->SetVisibilityState(isActive);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FMRadio::CanPlayChanged(int32_t aCanPlay)
+{
+  SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
+  return NS_OK;
+}
+
+void
+FMRadio::SetCanPlay(bool aCanPlay)
+{
+  IFMRadioService::Singleton()->EnableAudio(aCanPlay);
+}
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(FMRadio, nsDOMEventTargetHelper)
 
 END_FMRADIO_NAMESPACE
 
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -6,35 +6,40 @@
 #ifndef mozilla_dom_FMRadio_h
 #define mozilla_dom_FMRadio_h
 
 #include "FMRadioCommon.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/HalTypes.h"
 #include "nsWeakReference.h"
+#include "AudioChannelAgent.h"
 
 class nsPIDOMWindow;
 class nsIScriptContext;
 
 BEGIN_FMRADIO_NAMESPACE
 
 class DOMRequest;
 
 class FMRadio MOZ_FINAL : public nsDOMEventTargetHelper
                         , public hal::SwitchObserver
                         , public FMRadioEventObserver
                         , public nsSupportsWeakReference
+                        , public nsIAudioChannelAgentCallback
+                        , public nsIDOMEventListener
+
 {
   friend class FMRadioRequest;
 
 public:
   FMRadio();
 
   NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
   /* hal::SwitchObserver */
   virtual void Notify(const hal::SwitchEvent& aEvent) MOZ_OVERRIDE;
@@ -73,20 +78,27 @@ public:
 
   already_AddRefed<DOMRequest> CancelSeek();
 
   IMPL_EVENT_HANDLER(enabled);
   IMPL_EVENT_HANDLER(disabled);
   IMPL_EVENT_HANDLER(antennaavailablechange);
   IMPL_EVENT_HANDLER(frequencychange);
 
+  // nsIDOMEventListener
+  NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
+
 private:
   ~FMRadio();
 
+  void SetCanPlay(bool aCanPlay);
+
   hal::SwitchState mHeadphoneState;
   bool mHasInternalAntenna;
   bool mIsShutdown;
+
+  nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 };
 
 END_FMRADIO_NAMESPACE
 
 #endif // mozilla_dom_FMRadio_h
 
--- a/dom/fmradio/FMRadioService.cpp
+++ b/dom/fmradio/FMRadioService.cpp
@@ -118,22 +118,18 @@ public:
   NS_IMETHOD Run()
   {
     FMRadioSettings info;
     info.upperLimit() = mUpperLimit;
     info.lowerLimit() = mLowerLimit;
     info.spaceType() = mSpaceType;
 
     EnableFMRadio(info);
+    IFMRadioService::Singleton()->EnableAudio(true);
 
-    nsCOMPtr<nsIAudioManager> audioManager =
-      do_GetService(NS_AUDIOMANAGER_CONTRACTID);
-    audioManager->SetFmRadioAudioEnabled(true);
-
-    // TODO apply path from bug 862899: AudioChannelAgent per process
     return NS_OK;
   }
 
 private:
   int32_t mUpperLimit;
   int32_t mLowerLimit;
   int32_t mSpaceType;
 };
@@ -204,21 +200,17 @@ class DisableRunnable MOZ_FINAL : public
 public:
   DisableRunnable() { }
 
   NS_IMETHOD Run()
   {
     // Fix Bug 796733. DisableFMRadio should be called before
     // SetFmRadioAudioEnabled to prevent the annoying beep sound.
     DisableFMRadio();
-
-    nsCOMPtr<nsIAudioManager> audioManager =
-      do_GetService(NS_AUDIOMANAGER_CONTRACTID);
-
-    audioManager->SetFmRadioAudioEnabled(false);
+    IFMRadioService::Singleton()->EnableAudio(false);
 
     return NS_OK;
   }
 };
 
 class SetFrequencyRunnable MOZ_FINAL : public nsRunnable
 {
 public:
@@ -294,16 +286,34 @@ FMRadioService::RemoveObserver(FMRadioEv
   {
     // Turning off the FM radio HW because observer list is empty.
     if (IsFMRadioOn()) {
       NS_DispatchToMainThread(new DisableRunnable());
     }
   }
 }
 
+void
+FMRadioService::EnableAudio(bool aAudioEnabled)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsIAudioManager> audioManager =
+    do_GetService("@mozilla.org/telephony/audiomanager;1");
+  if (!audioManager) {
+    return;
+  }
+
+  bool AudioEnabled;
+  audioManager->GetFmRadioAudioEnabled(&AudioEnabled);
+  if (AudioEnabled != aAudioEnabled) {
+    audioManager->SetFmRadioAudioEnabled(aAudioEnabled);
+  }
+}
+
 /**
  * Round the frequency to match the range of frequency and the channel width. If
  * the given frequency is out of range, return 0. For example:
  *  - lower: 87500KHz, upper: 108000KHz, channel width: 200KHz
  *    87.6MHz is rounded to 87700KHz
  *    87.58MHz is rounded to 87500KHz
  *    87.49MHz is rounded to 87500KHz
  *    109MHz is not rounded, 0 will be returned
--- a/dom/fmradio/FMRadioService.h
+++ b/dom/fmradio/FMRadioService.h
@@ -112,16 +112,19 @@ public:
    *   - StateChangedEvent
    *   - FrequencyChangedEvent
    *
    * Called by FMRadio and FMRadioParent.
    */
   virtual void AddObserver(FMRadioEventObserver* aObserver) = 0;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) = 0;
 
+  // Enable/Disable FMRadio
+  virtual void EnableAudio(bool aAudioEnabled) = 0;
+
   /**
    * Static method to return the singleton instance. If it's in the child
    * process, we will get an object of FMRadioChild.
    */
   static IFMRadioService* Singleton();
 };
 
 enum FMRadioState
@@ -159,16 +162,18 @@ public:
                             FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection,
                     FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void CancelSeek(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
 
   virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
 
+  virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
+
   /* FMRadioObserver */
   void Notify(const hal::FMRadioOperationInformation& aInfo) MOZ_OVERRIDE;
 
   NS_DECL_NSIOBSERVER
 
 protected:
   FMRadioService();
 
--- a/dom/fmradio/ipc/FMRadioChild.cpp
+++ b/dom/fmradio/ipc/FMRadioChild.cpp
@@ -160,16 +160,22 @@ FMRadioChild::AllocPFMRadioRequestChild(
 
 bool
 FMRadioChild::DeallocPFMRadioRequestChild(PFMRadioRequestChild* aActor)
 {
   delete aActor;
   return true;
 }
 
+void
+FMRadioChild::EnableAudio(bool aAudioEnabled)
+{
+  SendEnableAudio(aAudioEnabled);
+}
+
 // static
 FMRadioChild*
 FMRadioChild::Singleton()
 {
   MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Default);
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!sFMRadioChild) {
--- a/dom/fmradio/ipc/FMRadioChild.h
+++ b/dom/fmradio/ipc/FMRadioChild.h
@@ -46,16 +46,18 @@ public:
                             FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void Seek(mozilla::hal::FMRadioSeekDirection aDirection,
                     FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
   virtual void CancelSeek(FMRadioReplyRunnable* aReplyRunnable) MOZ_OVERRIDE;
 
   virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
   virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
 
+  virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
+
   /* PFMRadioChild */
   virtual bool
   Recv__delete__() MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyFrequencyChanged(const double& aFrequency) MOZ_OVERRIDE;
 
   virtual bool
--- a/dom/fmradio/ipc/FMRadioParent.cpp
+++ b/dom/fmradio/ipc/FMRadioParent.cpp
@@ -92,10 +92,17 @@ FMRadioParent::Notify(const FMRadioEvent
         IFMRadioService::Singleton()->GetFrequency());
       break;
     default:
       NS_RUNTIMEABORT("not reached");
       break;
   }
 }
 
+bool
+FMRadioParent::RecvEnableAudio(const bool& aAudioEnabled)
+{
+  IFMRadioService::Singleton()->EnableAudio(aAudioEnabled);
+  return true;
+}
+
 END_FMRADIO_NAMESPACE
 
--- a/dom/fmradio/ipc/FMRadioParent.h
+++ b/dom/fmradio/ipc/FMRadioParent.h
@@ -28,14 +28,17 @@ public:
   virtual PFMRadioRequestParent*
   AllocPFMRadioRequestParent(const FMRadioRequestArgs& aArgs) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPFMRadioRequestParent(PFMRadioRequestParent* aActor) MOZ_OVERRIDE;
 
   /* FMRadioEventObserver */
   virtual void Notify(const FMRadioEventType& aType) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvEnableAudio(const bool& aAudioEnabled) MOZ_OVERRIDE;
 };
 
 END_FMRADIO_NAMESPACE
 
 #endif // mozilla_dom_fmradioparent_h__
 
--- a/dom/fmradio/ipc/PFMRadio.ipdl
+++ b/dom/fmradio/ipc/PFMRadio.ipdl
@@ -81,13 +81,18 @@ parent:
    *
    * We don't have separate Enable/SetFrequency/etc. methods instead here,
    * because we can leverage the IPC messaging mechanism to manage the mapping
    * of the asynchronous request and the DOMRequest we returned to the caller
    * on web content, otherwise, we have to do the mapping stuff manually which
    * is more error prone.
    */
   PFMRadioRequest(FMRadioRequestArgs requestType);
+
+  /**
+   * Enable/Disable audio
+   */
+  EnableAudio(bool audioEnabled);
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -136,17 +136,17 @@ APZCTreeManager::UpdatePanZoomController
           apzc->SetPrevSibling(nullptr);
           apzc->SetLastChild(nullptr);
         }
         APZC_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, container->GetFrameMetrics().mScrollId);
 
         apzc->NotifyLayersUpdated(container->GetFrameMetrics(),
                                         aIsFirstPaint && (aLayersId == aFirstPaintLayersId));
 
-        LayerRect visible = ScreenRect(container->GetFrameMetrics().mCompositionBounds) * ScreenToLayerScale(1.0);
+        ScreenRect visible(container->GetFrameMetrics().mCompositionBounds);
         apzc->SetLayerHitTestData(visible, aTransform, aLayer->GetTransform());
         APZC_LOG("Setting rect(%f %f %f %f) as visible region for APZC %p\n", visible.x, visible.y,
                                                                               visible.width, visible.height,
                                                                               apzc);
 
         // Bind the APZC instance into the tree of APZCs
         if (aNextSibling) {
           aNextSibling->SetPrevSibling(apzc);
@@ -228,17 +228,17 @@ ApplyTransform(nsIntPoint* aPoint, const
   aPoint->y = NS_lround(result.y);
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(const InputData& aEvent)
 {
   nsEventStatus result = nsEventStatus_eIgnore;
   gfx3DMatrix transformToApzc;
-  gfx3DMatrix transformToScreen;
+  gfx3DMatrix transformToGecko;
   switch (aEvent.mInputType) {
     case MULTITOUCH_INPUT: {
       const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
       if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_START) {
         mApzcForInputBlock = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[0].mScreenPoint));
         for (size_t i = 1; i < multiTouchInput.mTouches.Length(); i++) {
           nsRefPtr<AsyncPanZoomController> apzc2 = GetTargetAPZC(ScreenPoint(multiTouchInput.mTouches[i].mScreenPoint));
           mApzcForInputBlock = CommonAncestor(mApzcForInputBlock.get(), apzc2.get());
@@ -246,17 +246,17 @@ APZCTreeManager::ReceiveInputEvent(const
           // For now, we only ever want to do pinching on the root APZC for a given layers id. So
           // when we find the common ancestor of multiple points, also walk up to the root APZC.
           mApzcForInputBlock = RootAPZCForLayersId(mApzcForInputBlock);
           APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", mApzcForInputBlock.get());
         }
 
         // Cache transformToApzc so it can be used for future events in this block.
         if (mApzcForInputBlock) {
-          GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
+          GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToGecko);
           mCachedTransformToApzcForInputBlock = transformToApzc;
         }
       } else if (mApzcForInputBlock) {
         APZC_LOG("Re-using APZC %p as continuation of event block\n", mApzcForInputBlock.get());
       }
       if (mApzcForInputBlock) {
         // Use the cached transform to compute the point to send to the APZC.
         // This ensures that the sequence of touch points an APZC sees in an
@@ -274,43 +274,43 @@ APZCTreeManager::ReceiveInputEvent(const
           mApzcForInputBlock = nullptr;
         }
       }
       break;
     } case PINCHGESTURE_INPUT: {
       const PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint);
       if (apzc) {
-        GetInputTransforms(apzc, transformToApzc, transformToScreen);
+        GetInputTransforms(apzc, transformToApzc, transformToGecko);
         PinchGestureInput inputForApzc(pinchInput);
         ApplyTransform(&(inputForApzc.mFocusPoint), transformToApzc);
         result = apzc->ReceiveInputEvent(inputForApzc);
       }
       break;
     } case TAPGESTURE_INPUT: {
       const TapGestureInput& tapInput = aEvent.AsTapGestureInput();
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(tapInput.mPoint));
       if (apzc) {
-        GetInputTransforms(apzc, transformToApzc, transformToScreen);
+        GetInputTransforms(apzc, transformToApzc, transformToGecko);
         TapGestureInput inputForApzc(tapInput);
         ApplyTransform(&(inputForApzc.mPoint), transformToApzc);
         result = apzc->ReceiveInputEvent(inputForApzc);
       }
       break;
     }
   }
   return result;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent,
                                         ScreenPoint aPoint)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aPoint);
-  gfx3DMatrix transformToApzc, transformToScreen;
+  gfx3DMatrix transformToApzc, transformToGecko;
   // Reset the cached apz transform
   mCachedTransformToApzcForInputBlock = transformToApzc;
   if (!apzc) {
     return nullptr;
   }
   for (size_t i = 1; i < aEvent.touches.Length(); i++) {
     nsIntPoint point = aEvent.touches[i]->mRefPoint;
     nsRefPtr<AsyncPanZoomController> apzc2 =
@@ -319,17 +319,17 @@ APZCTreeManager::GetTouchInputBlockAPZC(
     APZC_LOG("Using APZC %p as the common ancestor\n", apzc.get());
     // For now, we only ever want to do pinching on the root APZC for a given layers id. So
     // when we find the common ancestor of multiple points, also walk up to the root APZC.
     apzc = RootAPZCForLayersId(apzc);
     APZC_LOG("Using APZC %p as the root APZC for multi-touch\n", apzc.get());
   }
   if (apzc) {
     // Cache apz transform so it can be used for future events in this block.
-    GetInputTransforms(apzc, mCachedTransformToApzcForInputBlock, transformToScreen);
+    GetInputTransforms(apzc, mCachedTransformToApzcForInputBlock, transformToGecko);
   }
   return apzc.get();
 }
 
 nsEventStatus
 APZCTreeManager::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
                                    WidgetTouchEvent* aOutEvent)
 {
@@ -339,21 +339,21 @@ APZCTreeManager::ProcessTouchEvent(const
   gfx3DMatrix transformToApzc = mCachedTransformToApzcForInputBlock;
   MultiTouchInput inputForApzc(aEvent);
   for (size_t i = 0; i < inputForApzc.mTouches.Length(); i++) {
     ApplyTransform(&(inputForApzc.mTouches[i].mScreenPoint), transformToApzc);
   }
   nsEventStatus ret = mApzcForInputBlock->ReceiveInputEvent(inputForApzc);
 
   // For computing the event to pass back to Gecko, use the up-to-date transforms.
-  // This ensures that transformToApzc and transformToScreen are in sync
-  // (note that transformToScreen isn't cached).
-  gfx3DMatrix transformToScreen;
-  GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToScreen);
-  gfx3DMatrix outTransform = transformToApzc * transformToScreen;
+  // This ensures that transformToApzc and transformToGecko are in sync
+  // (note that transformToGecko isn't cached).
+  gfx3DMatrix transformToGecko;
+  GetInputTransforms(mApzcForInputBlock, transformToApzc, transformToGecko);
+  gfx3DMatrix outTransform = transformToApzc * transformToGecko;
   for (size_t i = 0; i < aOutEvent->touches.Length(); i++) {
     ApplyTransform(&(aOutEvent->touches[i]->mRefPoint), outTransform);
   }
 
   // If we have an mApzcForInputBlock and it's the end of the touch sequence
   // then null it out so we don't keep a dangling reference and leak things.
   if (aEvent.message == NS_TOUCH_CANCEL ||
       (aEvent.message == NS_TOUCH_END && aEvent.touches.Length() == 1)) {
@@ -366,39 +366,39 @@ nsEventStatus
 APZCTreeManager::ProcessMouseEvent(const WidgetMouseEvent& aEvent,
                                    WidgetMouseEvent* aOutEvent)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y));
   if (!apzc) {
     return nsEventStatus_eIgnore;
   }
   gfx3DMatrix transformToApzc;
-  gfx3DMatrix transformToScreen;
-  GetInputTransforms(apzc, transformToApzc, transformToScreen);
+  gfx3DMatrix transformToGecko;
+  GetInputTransforms(apzc, transformToApzc, transformToGecko);
   MultiTouchInput inputForApzc(aEvent);
   ApplyTransform(&(inputForApzc.mTouches[0].mScreenPoint), transformToApzc);
-  gfx3DMatrix outTransform = transformToApzc * transformToScreen;
+  gfx3DMatrix outTransform = transformToApzc * transformToGecko;
   ApplyTransform(&aOutEvent->refPoint, outTransform);
   return apzc->ReceiveInputEvent(inputForApzc);
 }
 
 nsEventStatus
 APZCTreeManager::ProcessEvent(const WidgetInputEvent& aEvent,
                               WidgetInputEvent* aOutEvent)
 {
   // Transform the refPoint
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(ScreenPoint(aEvent.refPoint.x, aEvent.refPoint.y));
   if (!apzc) {
     return nsEventStatus_eIgnore;
   }
   gfx3DMatrix transformToApzc;
-  gfx3DMatrix transformToScreen;
-  GetInputTransforms(apzc, transformToApzc, transformToScreen);
+  gfx3DMatrix transformToGecko;
+  GetInputTransforms(apzc, transformToApzc, transformToGecko);
   ApplyTransform(&(aOutEvent->refPoint), transformToApzc);
-  gfx3DMatrix outTransform = transformToApzc * transformToScreen;
+  gfx3DMatrix outTransform = transformToApzc * transformToGecko;
   ApplyTransform(&(aOutEvent->refPoint), outTransform);
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus
 APZCTreeManager::ReceiveInputEvent(const WidgetInputEvent& aEvent,
                                    WidgetInputEvent* aOutEvent)
 {
@@ -537,25 +537,25 @@ APZCTreeManager::ClearTree()
 void
 APZCTreeManager::HandleOverscroll(AsyncPanZoomController* aChild, ScreenPoint aStartPoint, ScreenPoint aEndPoint)
 {
   AsyncPanZoomController* parent = aChild->GetParent();
   if (parent == nullptr)
     return;
 
   gfx3DMatrix transformToApzc;
-  gfx3DMatrix transformToScreen;  // ignored
+  gfx3DMatrix transformToGecko;  // ignored
 
   // Convert start and end points to untransformed screen coordinates.
-  GetInputTransforms(aChild, transformToApzc, transformToScreen);
+  GetInputTransforms(aChild, transformToApzc, transformToGecko);
   ApplyTransform(&aStartPoint, transformToApzc.Inverse());
   ApplyTransform(&aEndPoint, transformToApzc.Inverse());
 
   // Convert start and end points to parent's transformed screen coordinates.
-  GetInputTransforms(parent, transformToApzc, transformToScreen);
+  GetInputTransforms(parent, transformToApzc, transformToGecko);
   ApplyTransform(&aStartPoint, transformToApzc);
   ApplyTransform(&aEndPoint, transformToApzc);
 
   parent->AttemptScroll(aStartPoint, aEndPoint);
 }
 
 bool
 APZCTreeManager::HitTestAPZC(const ScreenPoint& aPoint)
@@ -624,46 +624,58 @@ APZCTreeManager::FindTargetAPZC(AsyncPan
 AsyncPanZoomController*
 APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint)
 {
   // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
   // explained in the comment on GetInputTransforms. This function will recurse with aApzc at L and P, and the
   // comments explain what values are stored in the variables at these two levels. All the comments
   // use standard matrix notation where the leftmost matrix in a multiplication is applied first.
 
-  // ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
-  //                    and RC.Inverse() * QC.Inverse()                at recursion level for P.
+  // ancestorUntransform takes points from aApzc's parent APZC's screen coordinates
+  // to aApzc's screen coordinates.
+  // It is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
+  //   and RC.Inverse() * QC.Inverse()                at recursion level for P.
   gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
-  // asyncUntransform is LA.Inverse() at recursion level for L,
-  //                 and PA.Inverse() at recursion level for P.
-  gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
-  // untransformSinceLastApzc is OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() * LC.Inverse() at L,
-  //                         and RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse()                at P.
-  gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * aApzc->GetCSSTransform().Inverse();
-  // untransformed is the user input in L's layer space at L,
-  //                             and in P's layer space at P.
-  gfxPoint untransformed = untransformSinceLastApzc.ProjectPoint(aHitTestPoint);
-  APZC_LOG("Untransformed %f %f to %f %f for APZC %p\n", aHitTestPoint.x, aHitTestPoint.y, untransformed.x, untransformed.y, aApzc);
+
+  // Hit testing for this layer is performed in aApzc's screen coordinates.
+  gfxPoint hitTestPointForThisLayer = ancestorUntransform.ProjectPoint(aHitTestPoint);
+  APZC_LOG("Untransformed %f %f to screen coordinates %f %f for hit-testing APZC %p\n",
+           aHitTestPoint.x, aHitTestPoint.y,
+           hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
+
+  // myUntransform takes points from aApzc's screen coordinates
+  // to aApzc's layer coordinates (which are aApzc's children's screen coordinates).
+  // It is LA.Inverse() * LC.Inverse() at L
+  //   and PA.Inverse() * PC.Inverse() at P.
+  gfx3DMatrix myUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse()
+                            * aApzc->GetCSSTransform().Inverse();
+
+  // Hit testing for child layers is performed in aApzc's layer coordinates.
+  gfxPoint hitTestPointForChildLayers = myUntransform.ProjectPoint(hitTestPointForThisLayer);
+  APZC_LOG("Untransformed %f %f to layer coordinates %f %f for APZC %p\n",
+           aHitTestPoint.x, aHitTestPoint.y,
+           hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, aApzc);
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
   for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    AsyncPanZoomController* match = GetAPZCAtPoint(child, untransformed);
+    AsyncPanZoomController* match = GetAPZCAtPoint(child, hitTestPointForChildLayers);
     if (match) {
       return match;
     }
   }
-  if (aApzc->VisibleRegionContains(LayerPoint(untransformed.x, untransformed.y))) {
-    APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n", untransformed.x, untransformed.y, aApzc);
+  if (aApzc->VisibleRegionContains(ScreenPoint(hitTestPointForThisLayer.x, hitTestPointForThisLayer.y))) {
+    APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
+             hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
     return aApzc;
   }
   return nullptr;
 }
 
-/* This function sets the aTransformToApzcOut and aTransformToScreenOut out-parameters
+/* This function sets the aTransformToApzcOut and aTransformToGeckoOut out-parameters
    to some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
    is the layer that corresponds to the returned APZC instance, and layer R is the root
    of the layer tree. Layer M is the parent of L, N is the parent of M, and so on.
    When layer L is displayed to the screen by the compositor, the set of transforms that
    are applied to L are (in order from top to bottom):
 
         L's CSS transform      (hereafter referred to as transform matrix LC)
@@ -701,63 +713,63 @@ APZCTreeManager::GetAPZCAtPoint(AsyncPan
         ...
         RC
    This sequence can be simplified and refactored to the following:
         aTransformToApzcOut
         LA.Inverse()
         MC
         ...
         RC
-   Since aTransformToApzcOut is already one of the out-parameters, we set aTransformToScreenOut
+   Since aTransformToApzcOut is already one of the out-parameters, we set aTransformToGeckoOut
    to the remaining transforms (LA.Inverse() * MC * ... * RC), so that the caller code can
    combine it with aTransformToApzcOut to get the final transform required in this case.
 
    Note that for many of these layers, there will be no AsyncPanZoomController attached, and
    so the async transform will be the identity transform. So, in the example above, if layers
    L and P have APZC instances attached, MA, NA, OA, QA, and RA will be identity transforms.
    Additionally, for space-saving purposes, each APZC instance stores its layers individual
    CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for
    layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC).
    The APZCs also obviously have LA and PA, so all of the above transformation combinations
    required can be generated.
  */
 void
 APZCTreeManager::GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
-                                    gfx3DMatrix& aTransformToScreenOut)
+                                    gfx3DMatrix& aTransformToGeckoOut)
 {
   // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
   // explained in the comment above. This function is called with aApzc at L, and the loop
   // below performs one iteration, where parent is at P. The comments explain what values are stored
   // in the variables at these two levels. All the comments use standard matrix notation where the
   // leftmost matrix in a multiplication is applied first.
 
   // ancestorUntransform is OC.Inverse() * NC.Inverse() * MC.Inverse()
   gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
   // asyncUntransform is LA.Inverse()
   gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
 
   // aTransformToApzcOut is initialized to OC.Inverse() * NC.Inverse() * MC.Inverse()
   aTransformToApzcOut = ancestorUntransform;
-  // aTransformToScreenOut is initialized to LA.Inverse() * MC * NC * OC
-  aTransformToScreenOut = asyncUntransform * aApzc->GetAncestorTransform();
+  // aTransformToGeckoOut is initialized to LA.Inverse() * MC * NC * OC
+  aTransformToGeckoOut = asyncUntransform * aApzc->GetAncestorTransform();
 
   for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; parent = parent->GetParent()) {
     // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent == P
     ancestorUntransform = parent->GetAncestorTransform().Inverse();
     // asyncUntransform is updated to PA.Inverse() when parent == P
     asyncUntransform = gfx3DMatrix(parent->GetCurrentAsyncTransform()).Inverse();
     // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse()
     gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * parent->GetCSSTransform().Inverse();
 
     // aTransformToApzcOut is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() * OC.Inverse() * NC.Inverse() * MC.Inverse()
     aTransformToApzcOut = untransformSinceLastApzc * aTransformToApzcOut;
-    // aTransformToScreenOut is LA.Inverse() * MC * NC * OC * PC * QC * RC
-    aTransformToScreenOut = aTransformToScreenOut * parent->GetCSSTransform() * parent->GetAncestorTransform();
+    // aTransformToGeckoOut is LA.Inverse() * MC * NC * OC * PC * QC * RC
+    aTransformToGeckoOut = aTransformToGeckoOut * parent->GetCSSTransform() * parent->GetAncestorTransform();
 
-    // The above values for aTransformToApzcOut and aTransformToScreenOut when parent == P match
+    // The above values for aTransformToApzcOut and aTransformToGeckoOut when parent == P match
     // the required output as explained in the comment above GetTargetAPZC. Note that any missing terms
     // are async transforms that are guaranteed to be identity transforms.
   }
 }
 
 AsyncPanZoomController*
 APZCTreeManager::CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2)
 {
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -272,17 +272,17 @@ public:
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
      about it going away. These are public for testing code and generally should not be
      used by other production code.
   */
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScrollableLayerGuid& aGuid);
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint);
   void GetInputTransforms(AsyncPanZoomController *aApzc, gfx3DMatrix& aTransformToApzcOut,
-                          gfx3DMatrix& aTransformToScreenOut);
+                          gfx3DMatrix& aTransformToGeckoOut);
 private:
   /* Helpers */
   AsyncPanZoomController* FindTargetAPZC(AsyncPanZoomController* aApzc, const ScrollableLayerGuid& aGuid);
   AsyncPanZoomController* GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint);
   AsyncPanZoomController* CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2);
   AsyncPanZoomController* RootAPZCForLayersId(AsyncPanZoomController* aApzc);
   AsyncPanZoomController* GetTouchInputBlockAPZC(const WidgetTouchEvent& aEvent, ScreenPoint aPoint);
   nsEventStatus ProcessTouchEvent(const WidgetTouchEvent& touchEvent, WidgetTouchEvent* aOutEvent);
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -650,41 +650,40 @@ private:
   nsRefPtr<AsyncPanZoomController> mPrevSibling;
   nsRefPtr<AsyncPanZoomController> mParent;
 
   /* The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
-  void SetLayerHitTestData(const LayerRect& aRect, const gfx3DMatrix& aTransformToLayer,
+  void SetLayerHitTestData(const ScreenRect& aRect, const gfx3DMatrix& aTransformToLayer,
                            const gfx3DMatrix& aTransformForLayer) {
     mVisibleRect = aRect;
     mAncestorTransform = aTransformToLayer;
     mCSSTransform = aTransformForLayer;
   }
 
   gfx3DMatrix GetAncestorTransform() const {
     return mAncestorTransform;
   }
 
   gfx3DMatrix GetCSSTransform() const {
     return mCSSTransform;
   }
 
-  bool VisibleRegionContains(const LayerPoint& aPoint) const {
+  bool VisibleRegionContains(const ScreenPoint& aPoint) const {
     return mVisibleRect.Contains(aPoint);
   }
 
 private:
-  /* This is the viewport of the layer that this APZC corresponds to, in
-   * layer pixels. It position here does not account for any transformations
-   * applied to any layers, whether they are CSS transforms or async
-   * transforms. */
-  LayerRect mVisibleRect;
+  /* This is the visible region of the layer that this APZC corresponds to, in
+   * that layer's screen pixels (the same coordinate system in which this APZC
+   * receives events in ReceiveInputEvent()). */
+  ScreenRect mVisibleRect;
   /* This is the cumulative CSS transform for all the layers between the parent
    * APZC and this one (not inclusive) */
   gfx3DMatrix mAncestorTransform;
   /* This is the CSS transform for this APZC's layer. */
   gfx3DMatrix mCSSTransform;
 };
 
 }
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -428,54 +428,72 @@ TEST(AsyncPanZoomController, OverScrollP
   ViewTransform viewTransformOut;
 
   // Pan down
   ApzcPan(apzc, time, touchStart, touchEnd);
   apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(1000), &viewTransformOut, pointOut);
   EXPECT_EQ(pointOut, ScreenPoint(0, 90));
 }
 
+// Layer tree for HitTesting1
 static already_AddRefed<mozilla::layers::Layer>
-CreateTestLayerTree(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
-  const char* layerTreeSyntax = "c(ttccc(c(c)))";
-  // LayerID                     0 12345 6 7
+CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
+  const char* layerTreeSyntax = "c(ttcc)";
+  // LayerID                     0 1234
   nsIntRegion layerVisibleRegion[] = {
     nsIntRegion(nsIntRect(0,0,100,100)),
     nsIntRegion(nsIntRect(0,0,100,100)),
     nsIntRegion(nsIntRect(10,10,20,20)),
     nsIntRegion(nsIntRect(10,10,20,20)),
     nsIntRegion(nsIntRect(5,5,20,20)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
   };
   gfx3DMatrix transforms[] = {
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
-    gfx3DMatrix(),
-    gfx3DMatrix(),
-    gfx3DMatrix(),
   };
   return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers);
 }
 
+// Layer Tree for HitTesting2
+static already_AddRefed<mozilla::layers::Layer>
+CreateTestLayerTree2(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
+  const char* layerTreeSyntax = "c(cc(c))";
+  // LayerID                     0 12 3
+  nsIntRegion layerVisibleRegion[] = {
+    nsIntRegion(nsIntRect(0,0,100,100)),
+    nsIntRegion(nsIntRect(10,10,40,40)),
+    nsIntRegion(nsIntRect(10,60,40,40)),
+    nsIntRegion(nsIntRect(10,60,40,40)),
+  };
+  gfx3DMatrix transforms[] = {
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+  };
+  return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers);
+}
+
 static void
-SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, MockContentController* mcc)
+SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
+                          // The scrollable rect is only used in HitTesting2,
+                          // HitTesting1 doesn't care about it.
+                          CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1))
 {
   ContainerLayer* container = aLayer->AsContainerLayer();
   FrameMetrics metrics;
   metrics.mScrollId = aScrollId;
   nsIntRect layerBound = aLayer->GetVisibleRegion().GetBounds();
   metrics.mCompositionBounds = ScreenIntRect(layerBound.x, layerBound.y,
                                              layerBound.width, layerBound.height);
-  metrics.mViewport = CSSRect(layerBound.x, layerBound.y,
-                              layerBound.width, layerBound.height);
+  metrics.mScrollableRect = aScrollableRect;
+  metrics.mScrollOffset = CSSPoint(0, 0);
   container->SetFrameMetrics(metrics);
 }
 
 static gfxPoint
 NudgeToIntegers(const gfxPoint& aPoint)
 {
   // gfxPoint has doubles but NudgeToInteger takes
   // floats so use local vars. The loss in precision
@@ -485,141 +503,198 @@ NudgeToIntegers(const gfxPoint& aPoint)
   float y = aPoint.y;
   NudgeToInteger(&x);
   NudgeToInteger(&y);
   return gfxPoint(x, y);
 }
 
 static already_AddRefed<AsyncPanZoomController>
 GetTargetAPZC(APZCTreeManager* manager, const ScreenPoint& aPoint,
-              gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToScreenOut)
+              gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToGeckoOut)
 {
   nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint);
   if (hit) {
-    manager->GetInputTransforms(hit.get(), aTransformToApzcOut, aTransformToScreenOut);
+    manager->GetInputTransforms(hit.get(), aTransformToApzcOut, aTransformToGeckoOut);
   }
   return hit.forget();
 }
 
-TEST(APZCTreeManager, GetAPZCAtPoint) {
+// A simple hit testing test that doesn't involve any transforms on layers.
+TEST(APZCTreeManager, HitTesting1) {
   nsTArray<nsRefPtr<Layer> > layers;
   nsRefPtr<LayerManager> lm;
-  nsRefPtr<Layer> root = CreateTestLayerTree(lm, layers);
+  nsRefPtr<Layer> root = CreateTestLayerTree1(lm, layers);
 
   TimeStamp testStartTime = TimeStamp::Now();
   AsyncPanZoomController::SetFrameTime(testStartTime);
   nsRefPtr<MockContentController> mcc = new MockContentController();
   ScopedLayerTreeRegistration controller(0, root, mcc);
 
   nsRefPtr<APZCTreeManager> manager = new TestAPZCTreeManager();
   gfx3DMatrix transformToApzc;
-  gfx3DMatrix transformToScreen;
+  gfx3DMatrix transformToGecko;
 
   // No APZC attached so hit testing will return no APZC at (20,20)
-  nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(manager, ScreenPoint(20, 20), transformToApzc, transformToScreen);
+  nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(manager, ScreenPoint(20, 20), transformToApzc, transformToGecko);
   AsyncPanZoomController* nullAPZC = nullptr;
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(gfx3DMatrix(), transformToApzc);
-  EXPECT_EQ(gfx3DMatrix(), transformToScreen);
+  EXPECT_EQ(gfx3DMatrix(), transformToGecko);
 
   // Now we have a root APZC that will match the page
-  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
+  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
+  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
   EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
-  EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
+  EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15)));
 
   // Now we have a sub APZC with a better fit
-  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
+  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
   EXPECT_NE(root->AsContainerLayer()->GetAsyncPanZoomController(), layers[3]->AsContainerLayer()->GetAsyncPanZoomController());
-  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
+  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
   EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
-  EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
+  EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15)));
 
   // Now test hit testing when we have two scrollable layers
-  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
+  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
   EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
+  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
+  hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko);
   EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
-  EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
+  EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15)));
 
   // Hit test ouside the reach of layer[3,4] but inside root
-  hit = GetTargetAPZC(manager, ScreenPoint(90, 90), transformToApzc, transformToScreen);
+  hit = GetTargetAPZC(manager, ScreenPoint(90, 90), transformToApzc, transformToGecko);
   EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(90, 90)
   EXPECT_EQ(gfxPoint(90, 90), transformToApzc.Transform(gfxPoint(90, 90)));
-  EXPECT_EQ(gfxPoint(90, 90), transformToScreen.Transform(gfxPoint(90, 90)));
+  EXPECT_EQ(gfxPoint(90, 90), transformToGecko.Transform(gfxPoint(90, 90)));
 
   // Hit test ouside the reach of any layer
-  hit = GetTargetAPZC(manager, ScreenPoint(1000, 10), transformToApzc, transformToScreen);
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(gfx3DMatrix(), transformToApzc);
-  EXPECT_EQ(gfx3DMatrix(), transformToScreen);
-  hit = GetTargetAPZC(manager, ScreenPoint(-1000, 10), transformToApzc, transformToScreen);
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(gfx3DMatrix(), transformToApzc);
-  EXPECT_EQ(gfx3DMatrix(), transformToScreen);
-
-  // Test layer transform
-  gfx3DMatrix transform;
-  transform.ScalePost(0.1, 0.1, 1);
-  root->SetBaseTransform(transform);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(50, 50), transformToApzc, transformToScreen); // This point is now outside the root layer
+  hit = GetTargetAPZC(manager, ScreenPoint(1000, 10), transformToApzc, transformToGecko);
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(gfx3DMatrix(), transformToApzc);
-  EXPECT_EQ(gfx3DMatrix(), transformToScreen);
-
-  // This hit test will hit both layers[3] and layers[4]; layers[4] is later in the tree so
-  // it is a better match
-  hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 20)
-  EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
-  EXPECT_EQ(gfxPoint(2, 2), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 20))));
-
-  // Scale layer[4] outside the range
-  layers[4]->SetBaseTransform(transform);
-  // layer 4 effective visible screenrect: (0.05, 0.05, 0.2, 0.2)
-  // Does not contain (2, 2)
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 20)
-  EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
-  EXPECT_EQ(gfxPoint(2, 2), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 20))));
-
-  // Transformation chain to layer 7
-  SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2, mcc);
-
-  gfx3DMatrix translateTransform;
-  translateTransform.Translate(gfxPoint3D(10, 10, 0));
-  layers[5]->SetBaseTransform(translateTransform);
-
-  gfx3DMatrix translateTransform2;
-  translateTransform2.Translate(gfxPoint3D(-20, 0, 0));
-  layers[6]->SetBaseTransform(translateTransform2);
-
-  gfx3DMatrix translateTransform3;
-  translateTransform3.ScalePost(1,15,1);
-  layers[7]->SetBaseTransform(translateTransform3);
-
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  // layer 7 effective visible screenrect (0,16,4,60) but clipped by parent layers
-  hit = GetTargetAPZC(manager, ScreenPoint(1, 45), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[7]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 440), which is CSSPoint(20, 29)
-  EXPECT_EQ(gfxPoint(20, 440), NudgeToIntegers(transformToApzc.Transform(gfxPoint(1, 45))));
-  EXPECT_EQ(gfxPoint(1, 45), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 440))));
+  EXPECT_EQ(gfx3DMatrix(), transformToGecko);
+  hit = GetTargetAPZC(manager, ScreenPoint(-1000, 10), transformToApzc, transformToGecko);
+  EXPECT_EQ(nullAPZC, hit.get());
+  EXPECT_EQ(gfx3DMatrix(), transformToApzc);
+  EXPECT_EQ(gfx3DMatrix(), transformToGecko);
 
   manager->ClearTree();
 }
 
+// A more involved hit testing test that involves css and async transforms.
+TEST(APZCTreeManager, HitTesting2) {
+  nsTArray<nsRefPtr<Layer> > layers;
+  nsRefPtr<LayerManager> lm;
+  nsRefPtr<Layer> root = CreateTestLayerTree2(lm, layers);
 
+  TimeStamp testStartTime = TimeStamp::Now();
+  AsyncPanZoomController::SetFrameTime(testStartTime);
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  ScopedLayerTreeRegistration controller(0, root, mcc);
+
+  nsRefPtr<APZCTreeManager> manager = new TestAPZCTreeManager();
+  nsRefPtr<AsyncPanZoomController> hit;
+  gfx3DMatrix transformToApzc;
+  gfx3DMatrix transformToGecko;
+
+  // Set a CSS transform on one of the layers.
+  gfx3DMatrix transform;
+  transform.ScalePost(2, 1, 1);
+  layers[2]->SetBaseTransform(transform);
+
+  // Make some other layers scrollable.
+  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, CSSRect(0, 0, 200, 200));
+  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 80, 80));
+  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
+
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 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)
+
+  AsyncPanZoomController* apzcroot = root->AsContainerLayer()->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzc1 = layers[1]->AsContainerLayer()->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzc3 = layers[3]->AsContainerLayer()->GetAsyncPanZoomController();
+
+  // Hit an area that's clearly on the root layer but not any of the child layers.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 25), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(gfxPoint(75, 25), transformToApzc.Transform(gfxPoint(75, 25)));
+  EXPECT_EQ(gfxPoint(75, 25), transformToGecko.Transform(gfxPoint(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(manager, ScreenPoint(15, 75), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(gfxPoint(15, 75), transformToApzc.Transform(gfxPoint(15, 75)));
+  EXPECT_EQ(gfxPoint(15, 75), transformToGecko.Transform(gfxPoint(15, 75)));
+
+  // Hit an area on layers[1].
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzc1, hit.get());
+  EXPECT_EQ(gfxPoint(25, 25), transformToApzc.Transform(gfxPoint(25, 25)));
+  EXPECT_EQ(gfxPoint(25, 25), transformToGecko.Transform(gfxPoint(25, 25)));
+
+  // Hit an area on layers[3].
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 75), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 75)));
+  // and transformToGecko should reapply it
+  EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(12.5, 75)));
+
+  // Hit an area on layers[3] that would be on the root if layers[2]
+  // weren't transformed.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(gfxPoint(37.5, 75), transformToApzc.Transform(gfxPoint(75, 75)));
+  // and transformToGecko should reapply it
+  EXPECT_EQ(gfxPoint(75, 75), transformToGecko.Transform(gfxPoint(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.
+  int time = 0;
+  // Silence GMock warnings about "uninteresting mock function calls".
+  EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(1);
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(2);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+  ApzcPan(apzcroot, time, 100, 50);
+
+  // Hit where layers[3] used to be. It should now hit the root.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75)));
+  // but transformToGecko does
+  EXPECT_EQ(gfxPoint(75, 125), transformToGecko.Transform(gfxPoint(75, 75)));
+
+  // Hit where layers[1] used to be and where layers[3] should now be.
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc unapplies both layers[2]'s css transform and the root's
+  // async trasnform
+  EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 25)));
+  // transformToGecko reapplies the css transform only (since Gecko doesn't
+  // know about async transforms)
+  EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(12.5, 75)));
+
+  manager->ClearTree();
+}
--- a/webapprt/prefs.js
+++ b/webapprt/prefs.js
@@ -44,16 +44,21 @@ pref("offline-apps.allow_by_default", tr
 pref("dom.mozTCPSocket.enabled", true);
 
 // Enable smooth scrolling
 pref("general.smoothScroll", true);
 
 // WebPayment
 pref("dom.mozPay.enabled", true);
 
+// Disable slow script dialog for apps
+pref("dom.max_script_run_time", 0);
+pref("dom.max_chrome_script_run_time", 0);
+
+
 #ifndef RELEASE_BUILD
 // Enable mozPay default provider
 pref("dom.payment.provider.0.name", "Firefox Marketplace");
 pref("dom.payment.provider.0.description", "marketplace.firefox.com");
 pref("dom.payment.provider.0.uri", "https://marketplace.firefox.com/mozpay/?req=");
 pref("dom.payment.provider.0.type", "mozilla/payments/pay/v1");
 pref("dom.payment.provider.0.requestMethod", "GET");
 #endif