Bug 750977: Implement glue code for asynchronous panning/zooming. r=jlebar,roc,vingtetun
authorChris Jones <jones.chris.g@gmail.com>
Thu, 19 Jul 2012 23:48:27 -0700
changeset 105886 19b28e14df61d51ff1666b1a64ca44a26e093231
parent 105885 67bb92bf9989e9aec99316cd2571cf6721b87ad0
child 105887 6bff6810a82e5c4bcddf2c9d35b917dde2fd13a8
push id214
push userakeybl@mozilla.com
push dateWed, 14 Nov 2012 20:38:59 +0000
treeherdermozilla-release@c8b08ec8e1aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, roc, vingtetun
bugs750977
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 750977: Implement glue code for asynchronous panning/zooming. r=jlebar,roc,vingtetun This is a rollup of three separate patches - Add nsIDocShell.asyncPanZoomEnabled. r=jlebar - Have BrowserElementChild service repaint requests and handle fallback synchronous scrolling (for now). r=jlebar,vingtetun - Glue async pan/zoom logic up between compositing, event dispatch, and repaint requests. r=roc
b2g/app/b2g.js
b2g/chrome/content/webapi.js
b2g/chrome/jar.mn
b2g/components/ProcessGlobal.js
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
dom/browser-element/BrowserElementChild.js
dom/browser-element/BrowserElementScrolling.js
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/ipc/jar.mn
gfx/layers/ipc/CompositorParent.cpp
gfx/layers/ipc/CompositorParent.h
layout/base/nsPresShell.cpp
layout/ipc/Makefile.in
layout/ipc/PRenderFrame.ipdl
layout/ipc/RenderFrameParent.cpp
layout/ipc/RenderFrameParent.h
layout/ipc/RenderFrameUtils.h
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -242,16 +242,17 @@ pref("editor.singleLine.pasteNewlines", 
 // The names of the preferences are to be in sync with nsEventStateManager.cpp
 pref("ui.dragThresholdX", 25);
 pref("ui.dragThresholdY", 25);
 
 // Layers Acceleration
 pref("layers.acceleration.disabled", false);
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.async-video.enabled", true);
+pref("layers.async-pan-zoom.enabled", true);
 
 // Web Notifications
 pref("notification.feature.enabled", true);
 
 // IndexedDB
 pref("indexedDB.feature.enabled", true);
 pref("dom.indexedDB.warningQuota", 5);
 
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -11,17 +11,16 @@ chrome.jar:
   content/dbg-browser-actors.js         (content/dbg-browser-actors.js)
   content/forms.js                      (content/forms.js)
   content/settings.js                   (content/settings.js)
 * content/shell.xul                     (content/shell.xul)
 * content/shell.js                      (content/shell.js)
 #ifndef ANDROID
   content/screen.js                     (content/screen.js)
 #endif
-  content/webapi.js                     (content/webapi.js)
   content/content.css                   (content/content.css)
   content/touchcontrols.css             (content/touchcontrols.css)
 
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
 % override chrome://global/skin/netError.css chrome://browser/content/netError.css
 % override chrome://global/skin/media/videocontrols.css chrome://browser/content/touchcontrols.css
 
   content/netError.xhtml                (content/netError.xhtml)
--- a/b2g/components/ProcessGlobal.js
+++ b/b2g/components/ProcessGlobal.js
@@ -13,17 +13,16 @@
  *
  * (It's written as an XPCOM service because it needs to watch
  * app-startup.)
  */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
-const kWebApiShimFile = 'chrome://browser/content/webapi.js';
 
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 function debug(msg) {
   log(msg);
 }
 function log(msg) {
@@ -36,39 +35,26 @@ ProcessGlobal.prototype = {
   classID: Components.ID('{1a94c87a-5ece-4d11-91e1-d29c29f21b28}'),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   observe: function pg_observe(subject, topic, data) {
     switch (topic) {
     case 'app-startup': {
       Services.obs.addObserver(this, 'console-api-log-event', false);
-      Services.obs.addObserver(this, 'in-process-browser-frame-shown', false);
-      Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
       break;
     }
     case 'console-api-log-event': {
       // Pipe `console` log messages to the nsIConsoleService which
       // writes them to logcat on Gonk.
       let message = subject.wrappedJSObject;
       let prefix = ('Content JS ' + message.level.toUpperCase() +
                     ' at ' + message.filename + ':' + message.lineNumber +
                     ' in ' + (message.functionName || 'anonymous') + ': ');
       Services.console.logStringMessage(prefix + Array.join(message.arguments,
                                                             ' '));
       break;
     }
-    case 'remote-browser-frame-shown':
-    case 'in-process-browser-frame-shown': {
-      let frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
-      let mm = frameLoader.messageManager;
-      try {
-        mm.loadFrameScript(kWebApiShimFile, true);
-      } catch (e) {
-        log('Error loading ' + kWebApiShimFile + ' as frame script: ' + e + '\n');
-      }
-      break;
-    }
     }
   },
 };
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([ProcessGlobal]);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=4 sw=4 tw=80 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/Util.h"
 
 #ifdef MOZ_LOGGING
 // so we can get logging even in release builds (but only for some things)
 #define FORCE_PR_LOG 1
 #endif
 
 #include "nsIBrowserDOMWindow.h"
@@ -196,16 +197,17 @@ static NS_DEFINE_CID(kDOMScriptObjectFac
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #if defined(DEBUG_bryner) || defined(DEBUG_chb)
 //#define DEBUG_DOCSHELL_FOCUS
 #define DEBUG_PAGE_CACHE
 #endif
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 // Number of documents currently loading
 static PRInt32 gNumberOfDocumentsLoading = 0;
 
 // Global count of existing docshells.
 static PRInt32 gDocShellCount = 0;
 
 // Global count of docshells with the private attribute set
@@ -12161,8 +12163,19 @@ nsDocShell::GetAppId(PRUint32* aAppId)
     nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
     if (!parent) {
         *aAppId = nsIScriptSecurityManager::NO_APP_ID;
         return NS_OK;
     }
 
     return parent->GetAppId(aAppId);
 }
+
+NS_IMETHODIMP
+nsDocShell::GetAsyncPanZoomEnabled(bool* aOut)
+{
+    if (TabChild* tabChild = GetTabChildFrom(this)) {
+        *aOut = tabChild->IsAsyncPanZoomEnabled();
+        return NS_OK;
+    }
+    *aOut = false;
+    return NS_OK;
+}
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -34,17 +34,17 @@ interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 
-[scriptable, builtinclass, uuid(c98f0f21-fe96-4f06-9978-0a9422a789fa)]
+[scriptable, builtinclass, uuid(05802c0d-3315-4245-b72e-cf92eb3118a3)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -644,9 +644,15 @@ interface nsIDocShell : nsISupports
    */
   [noscript] void setAppId(in unsigned long appId);
 
   /**
    * Returns the app id of the app the docshell is in. Returns
    * nsIScriptSecurityManager::NO_APP_ID if the docshell is not in an app.
    */
   readonly attribute unsigned long appId;
+
+  /** 
+   * True iff asynchronous panning and zooming is enabled for this
+   * docshell.
+   */
+  readonly attribute bool asyncPanZoomEnabled;
 };
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -1,21 +1,18 @@
 /* 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/. */
 
 "use strict";
 
-let Cu = Components.utils;
-let Ci = Components.interfaces;
-let Cc = Components.classes;
-let Cr = Components.results;
-
+let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Geometry.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 
 // Event whitelisted for bubbling.
 let whitelistedEvents = [
   Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE,   // Back button.
   Ci.nsIDOMKeyEvent.DOM_VK_SLEEP,    // Power button.
   Ci.nsIDOMKeyEvent.DOM_VK_CONTEXT_MENU,
   Ci.nsIDOMKeyEvent.DOM_VK_F5,       // Search button.
@@ -570,8 +567,15 @@ BrowserElementChild.prototype = {
 
     onStatusChange: function(webProgress, request, status, message) {},
     onProgressChange: function(webProgress, request, curSelfProgress,
                                maxSelfProgress, curTotalProgress, maxTotalProgress) {},
   },
 };
 
 var api = new BrowserElementChild();
+
+// FIXME/bug 775438: use a JSM?
+//
+// The code in this included file depends on the |addEventListener|,
+// |addMessageListener|, |content|, |Geometry| and |Services| symbols
+// being "exported" from here.
+#include BrowserElementScrolling.js
rename from b2g/chrome/content/webapi.js
rename to dom/browser-element/BrowserElementScrolling.js
--- a/b2g/chrome/content/webapi.js
+++ b/dom/browser-element/BrowserElementScrolling.js
@@ -1,28 +1,19 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* 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/. */
 
-'use strict';
-
-dump('======================= webapi.js ======================= \n');
-
-let { classes: Cc, interfaces: Ci, utils: Cu }  = Components;
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-Cu.import('resource://gre/modules/Services.jsm');
-Cu.import('resource://gre/modules/Geometry.jsm');
-
 const ContentPanning = {
   init: function cp_init() {
     ['mousedown', 'mouseup', 'mousemove'].forEach(function(type) {
       addEventListener(type, ContentPanning, true);
     });
+
+    addMessageListener("Viewport:Change", this._recvViewportChange.bind(this));
   },
 
   handleEvent: function cp_handleEvent(evt) {
     switch (evt.type) {
       case 'mousedown':
         this.onTouchStart(evt);
         break;
       case 'mousemove':
@@ -119,19 +110,19 @@ const ContentPanning = {
     if (!this.dragging)
       this.scrollCallback = null;
   },
 
   getPannable: function cp_getPannable(node) {
     if (!(node instanceof Ci.nsIDOMHTMLElement) || node.tagName == 'HTML')
       return [null, null];
 
-    let content = node.ownerDocument.defaultView;
+    let nodeContent = node.ownerDocument.defaultView;
     while (!(node instanceof Ci.nsIDOMHTMLBodyElement)) {
-      let style = content.getComputedStyle(node, null);
+      let style = nodeContent.getComputedStyle(node, null);
 
       let overflow = [style.getPropertyValue('overflow'),
                       style.getPropertyValue('overflow-x'),
                       style.getPropertyValue('overflow-y')];
 
       let rect = node.getBoundingClientRect();
       let isAuto = (overflow.indexOf('auto') != -1 &&
                    (rect.height < node.scrollHeight ||
@@ -139,17 +130,23 @@ const ContentPanning = {
 
       let isScroll = (overflow.indexOf('scroll') != -1);
       if (isScroll || isAuto)
         return [node, this._generateCallback(node)];
 
       node = node.parentNode;
     }
 
-    return [content, this._generateCallback(content)];
+    if (ContentPanning._asyncPanZoomForViewportFrame &&
+        nodeContent === content)
+      // The parent context is asynchronously panning and zooming our
+      // root scrollable frame, so don't use our synchronous fallback.
+      return [null, null];
+
+    return [nodeContent, this._generateCallback(nodeContent)];
   },
 
   _generateCallback: function cp_generateCallback(content) {
     function scroll(delta) {
       if (content instanceof Ci.nsIDOMHTMLElement) {
         let oldX = content.scrollLeft, oldY = content.scrollTop;
         content.scrollLeft += delta.x;
         content.scrollTop += delta.y;
@@ -171,16 +168,49 @@ const ContentPanning = {
                               .getService(Ci.inIDOMUtils);
   },
 
   _resetActive: function cp_resetActive() {
     let root = this.target.ownerDocument || this.target.document;
 
     const kStateActive = 0x00000001;
     this._domUtils.setContentState(root.documentElement, kStateActive);
+  },
+
+  get _asyncPanZoomForViewportFrame() {
+    return docShell.asyncPanZoomEnabled;
+  },
+
+  _recvViewportChange: function(data) {
+    let viewport = data.json;
+    let displayPort = viewport.displayPort;
+
+    let screenWidth = viewport.screenSize.width;
+    let screenHeight = viewport.screenSize.height;
+
+    let x = viewport.x;
+    let y = viewport.y;
+
+    let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    cwu.setCSSViewport(screenWidth, screenHeight);
+
+    // Set scroll position
+    cwu.setScrollPositionClampingScrollPortSize(
+      screenWidth / viewport.zoom, screenHeight / viewport.zoom);
+    content.scrollTo(x, y);
+    cwu.setResolution(displayPort.resolution, displayPort.resolution);
+
+    let element = null;
+    if (content.document && (element = content.document.documentElement)) {
+      cwu.setDisplayPortForElement(displayPort.left,
+                                   displayPort.top,
+                                   displayPort.width,
+                                   displayPort.height,
+                                   element);
+    }
   }
 };
 
 ContentPanning.init();
 
 // Min/max velocity of kinetic panning. This is in pixels/millisecond.
 const kMinVelocity = 0.4;
 const kMaxVelocity = 6;
@@ -344,9 +374,8 @@ const KineticPanning = {
       }
 
       content.mozRequestAnimationFrame(callback);
     }).bind(this);
 
     content.mozRequestAnimationFrame(callback);
   }
 };
-
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -8,28 +8,33 @@
 include protocol PContent;
 include protocol PContentDialog;
 include protocol PDocumentRenderer;
 include protocol PContentPermissionRequest;
 include protocol PRenderFrame;
 include protocol POfflineCacheUpdate;
 include protocol PIndexedDB;
 
+include "gfxMatrix.h";
+include "IPC/nsGUIEventIPC.h";
 include "mozilla/dom/TabMessageUtils.h";
-include "gfxMatrix.h";
+include "mozilla/layout/RenderFrameUtils.h";
 include "mozilla/net/NeckoMessageUtils.h";
-include "IPC/nsGUIEventIPC.h";
 
 using IPC::URI;
 using gfxMatrix;
+using gfxSize;
 using mozilla::layers::LayersBackend;
+using mozilla::layout::ScrollingBehavior;
 using mozilla::WindowsHandle;
 using nscolor;
 using nsCompositionEvent;
 using nsIMEUpdatePreference;
+using nsIntPoint;
+using nsIntRect;
 using nsIntSize;
 using nsKeyEvent;
 using nsMouseEvent;
 using nsMouseScrollEvent;
 using nsQueryContentEvent;
 using nsRect;
 using nsSelectionEvent;
 using nsTextEvent;
@@ -171,17 +176,18 @@ parent:
     PContentDialog(PRUint32 aType, nsCString aName, nsCString aFeatures,
                    PRInt32[] aIntParams, nsString[] aStringParams);
 
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
     sync PRenderFrame()
-        returns (LayersBackend backend, int32_t maxTextureSize, uint64_t layersId);
+        returns (ScrollingBehavior scrolling,
+                 LayersBackend backend, int32_t maxTextureSize, uint64_t layersId);
 
     /** 
      * Starts an offline application cache update.
      * @param manifestURI
      *   URI of the manifest to fetch, the application cache group ID
      * @param documentURI
      *   URI of the document that referred the manifest
      * @param clientID
@@ -236,16 +242,21 @@ child:
      * point.
      */
     Show(nsIntSize size);
 
     LoadURL(nsCString uri);
 
     UpdateDimensions(nsRect rect, nsIntSize size);
 
+    UpdateFrame(nsIntRect displayPort,
+                nsIntPoint scrollOffset,
+                gfxSize resolution,
+                nsIntRect screenSize);
+
     /**
      * Sending an activate message moves focus to the child.
      */
     Activate();
 
     Deactivate();
 
     /**
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -47,16 +47,17 @@
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXPCSecurityManager.h"
 #include "nsInterfaceHashtable.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsPresContext.h"
+#include "nsPrintfCString.h"
 #include "nsScriptLoader.h"
 #include "nsSerializationHelper.h"
 #include "nsThreadUtils.h"
 #include "nsWeakReference.h"
 #include "PCOMContentPermissionRequestChild.h"
 #include "TabChild.h"
 #include "xpcpublic.h"
 
@@ -621,16 +622,47 @@ TabChild::RecvUpdateDimensions(const nsR
     nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mWebNav);
     baseWin->SetPositionAndSize(0, 0, size.width, size.height,
                                 true);
 
     return true;
 }
 
 bool
+TabChild::RecvUpdateFrame(const nsIntRect& aDisplayPort,
+                          const nsIntPoint& aScrollOffset,
+                          const gfxSize& aResolution,
+                          const nsIntRect& aScreenSize)
+{
+    nsCString data;
+    data += nsPrintfCString("{ \"x\" : %d", aScrollOffset.x);
+    data += nsPrintfCString(", \"y\" : %d", aScrollOffset.y);
+    // We don't treat the x and y scales any differently for this
+    // semi-platform-specific code.
+    data += nsPrintfCString(", \"zoom\" : %f", aResolution.width);
+    data += nsPrintfCString(", \"displayPort\" : ");
+        data += nsPrintfCString("{ \"left\" : %d", aDisplayPort.X());
+        data += nsPrintfCString(", \"top\" : %d", aDisplayPort.Y());
+        data += nsPrintfCString(", \"width\" : %d", aDisplayPort.Width());
+        data += nsPrintfCString(", \"height\" : %d", aDisplayPort.Height());
+        data += nsPrintfCString(", \"resolution\" : %f", aResolution.width);
+        data += nsPrintfCString(" }");
+    data += nsPrintfCString(", \"screenSize\" : ");
+        data += nsPrintfCString("{ \"width\" : %d", aScreenSize.width);
+        data += nsPrintfCString(", \"height\" : %d", aScreenSize.height);
+        data += nsPrintfCString(" }");
+    data += nsPrintfCString(" }");
+
+    // Let the BrowserElementScrolling helper (if it exists) for this
+    // content manipulate the frame state.
+    return RecvAsyncMessage(NS_LITERAL_STRING("Viewport:Change"),
+                            NS_ConvertUTF8toUTF16(data));
+}
+
+bool
 TabChild::RecvActivate()
 {
   nsCOMPtr<nsIWebBrowserFocus> browser = do_QueryInterface(mWebNav);
   browser->Activate();
   return true;
 }
 
 bool TabChild::RecvDeactivate()
@@ -962,17 +994,18 @@ TabChild::RecvDestroy()
 
   // XXX what other code in ~TabChild() should we be running here?
   DestroyWindow();
 
   return Send__delete__(this);
 }
 
 PRenderFrameChild*
-TabChild::AllocPRenderFrame(LayersBackend* aBackend,
+TabChild::AllocPRenderFrame(ScrollingBehavior* aScrolling,
+                            LayersBackend* aBackend,
                             int32_t* aMaxTextureSize,
                             uint64_t* aLayersId)
 {
     return new RenderFrameChild();
 }
 
 bool
 TabChild::DeallocPRenderFrame(PRenderFrameChild* aFrame)
@@ -1035,17 +1068,17 @@ TabChild::InitWidget(const nsIntSize& si
         nsnull                  // nsDeviceContext
         );
 
     LayersBackend be;
     uint64_t id;
     int32_t maxTextureSize;
     RenderFrameChild* remoteFrame =
         static_cast<RenderFrameChild*>(SendPRenderFrameConstructor(
-                                           &be, &maxTextureSize, &id));
+                                           &mScrolling, &be, &maxTextureSize, &id));
     if (!remoteFrame) {
       NS_WARNING("failed to construct RenderFrame");
       return false;
     }
 
     PLayersChild* shadowManager = nsnull;
     if (id != 0) {
         // Pushing layers transactions directly to a separate
@@ -1098,16 +1131,22 @@ TabChild::NotifyPainted()
         // thread in the parent process.  It's wasteful but won't
         // result in unnecessary repainting or even composites
         // (usually, unless timing is unlucky), since they're
         // throttled.
         mRemoteFrame->SendNotifyCompositorTransaction();
     }
 }
 
+bool
+TabChild::IsAsyncPanZoomEnabled()
+{
+    return mScrolling == ASYNC_PAN_ZOOM;
+}
+
 NS_IMETHODIMP
 TabChild::GetMessageManager(nsIContentFrameMessageManager** aResult)
 {
   if (mTabChildGlobal) {
     NS_ADDREF(*aResult = mTabChildGlobal);
     return NS_OK;
   }
   *aResult = nsnull;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -38,16 +38,17 @@
 #include "nsIScriptContext.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDialogCreator.h"
 #include "nsIDialogParamBlock.h"
 #include "nsIPresShell.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
+#include "nsPIDOMWindow.h"
 #include "nsWeakReference.h"
 #include "nsITabChild.h"
 #include "mozilla/Attributes.h"
 
 struct gfxMatrix;
 
 namespace mozilla {
 namespace layout {
@@ -161,16 +162,20 @@ public:
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSIWINDOWPROVIDER
     NS_DECL_NSIDIALOGCREATOR
     NS_DECL_NSITABCHILD
 
     virtual bool RecvLoadURL(const nsCString& uri);
     virtual bool RecvShow(const nsIntSize& size);
     virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size);
+    virtual bool RecvUpdateFrame(const nsIntRect& aDisplayPort,
+                                      const nsIntPoint& aScrollOffset,
+                                      const gfxSize& aResolution,
+                                      const nsIntRect& aScreenSize);
     virtual bool RecvActivate();
     virtual bool RecvDeactivate();
     virtual bool RecvMouseEvent(const nsString& aType,
                                 const float&    aX,
                                 const float&    aY,
                                 const PRInt32&  aButton,
                                 const PRInt32&  aClickCount,
                                 const PRInt32&  aModifiers,
@@ -244,19 +249,23 @@ public:
 
     JSContext* GetJSContext() { return mCx; }
 
     nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
     void SetBackgroundColor(const nscolor& aColor);
 
     void NotifyPainted();
+
+    bool IsAsyncPanZoomEnabled();
+
 protected:
     NS_OVERRIDE
-    virtual PRenderFrameChild* AllocPRenderFrame(LayersBackend* aBackend,
+    virtual PRenderFrameChild* AllocPRenderFrame(ScrollingBehavior* aScrolling,
+                                                 LayersBackend* aBackend,
                                                  int32_t* aMaxTextureSize,
                                                  uint64_t* aLayersId);
     NS_OVERRIDE
     virtual bool DeallocPRenderFrame(PRenderFrameChild* aFrame);
     NS_OVERRIDE
     virtual bool RecvDestroy();
 
     nsEventStatus DispatchWidgetEvent(nsGUIEvent& event);
@@ -288,16 +297,17 @@ private:
 
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     PRUint32 mChromeFlags;
     nsIntRect mOuterRect;
     nscolor mLastBackgroundColor;
+    ScrollingBehavior mScrolling;
     bool mDidFakeShow;
     bool mIsBrowserFrame;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 inline TabChild*
 GetTabChildFrom(nsIDocShell* aDocShell)
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1,54 +1,58 @@
 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */
 /* 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 "TabParent.h"
+#include "base/basictypes.h"
 
+#include "IDBFactory.h"
+#include "IndexedDBParent.h"
+#include "mozilla/BrowserElementParent.h"
+#include "mozilla/docshell/OfflineCacheUpdateParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
+#include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layout/RenderFrameParent.h"
-#include "mozilla/docshell/OfflineCacheUpdateParent.h"
-
-#include "nsIURI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/unused.h"
+#include "nsCOMPtr.h"
+#include "nsContentPermissionHelper.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsEventDispatcher.h"
 #include "nsFocusManager.h"
-#include "nsCOMPtr.h"
-#include "nsServiceManagerUtils.h"
+#include "nsFrameLoader.h"
+#include "nsIContent.h"
 #include "nsIDOMElement.h"
-#include "nsEventDispatcher.h"
+#include "nsIDOMEvent.h"
 #include "nsIDOMEventTarget.h"
-#include "nsIWindowWatcher.h"
+#include "nsIDOMHTMLFrameElement.h"
 #include "nsIDOMWindow.h"
-#include "nsPIDOMWindow.h"
-#include "TabChild.h"
-#include "nsIDOMEvent.h"
-#include "nsFrameLoader.h"
+#include "nsIDialogCreator.h"
+#include "nsIPromptFactory.h"
+#include "nsIURI.h"
+#include "nsIMozBrowserFrame.h"
+#include "nsIViewManager.h"
+#include "nsIWidget.h"
+#include "nsIWindowWatcher.h"
 #include "nsNetUtil.h"
-#include "nsContentUtils.h"
-#include "nsContentPermissionHelper.h"
-#include "nsIDOMHTMLFrameElement.h"
-#include "nsIDialogCreator.h"
-#include "nsThreadUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
 #include "nsSerializationHelper.h"
-#include "nsIPromptFactory.h"
-#include "nsIContent.h"
-#include "nsIWidget.h"
-#include "nsIViewManager.h"
-#include "mozilla/unused.h"
-#include "nsDebug.h"
-#include "nsPrintfCString.h"
-#include "mozilla/BrowserElementParent.h"
-#include "IndexedDBParent.h"
-#include "IDBFactory.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "TabChild.h"
+#include "TabParent.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
+using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::widget;
 using namespace mozilla::dom::indexedDB;
 
 // The flags passed by the webProgress notifications are 16 bits shifted
 // from the ones registered by webProgressListeners.
 #define NOTIFY_FLAG_SHIFT 16
 
@@ -87,20 +91,18 @@ TabParent::SetOwnerElement(nsIDOMElement
 void
 TabParent::Destroy()
 {
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
   // destroy itself and send back __delete__().
   unused << SendDestroy();
 
-  for (size_t i = 0; i < ManagedPRenderFrameParent().Length(); ++i) {
-    RenderFrameParent* rfp =
-      static_cast<RenderFrameParent*>(ManagedPRenderFrameParent()[i]);
-    rfp->Destroy();
+  if (RenderFrameParent* frame = GetRenderFrame()) {
+    frame->Destroy();
   }
 }
 
 bool
 TabParent::Recv__delete__()
 {
   ContentParent* cp = static_cast<ContentParent*>(Manager());
   cp->NotifyTabDestroyed(this);
@@ -198,17 +200,29 @@ TabParent::Show(const nsIntSize& size)
     // sigh
     mShown = true;
     unused << SendShow(size);
 }
 
 void
 TabParent::UpdateDimensions(const nsRect& rect, const nsIntSize& size)
 {
-    unused << SendUpdateDimensions(rect, size);
+  unused << SendUpdateDimensions(rect, size);
+  if (RenderFrameParent* rfp = GetRenderFrame()) {
+    rfp->NotifyDimensionsChanged(size.width, size.height);
+  }
+}
+
+void
+TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
+{
+  unused << SendUpdateFrame(aFrameMetrics.mDisplayPort,
+                            aFrameMetrics.mViewportScrollOffset,
+                            aFrameMetrics.mResolution,
+                            aFrameMetrics.mViewport);
 }
 
 void
 TabParent::Activate()
 {
     mActive = true;
     unused << SendActivate();
 }
@@ -297,32 +311,40 @@ TabParent::SendKeyEvent(const nsAString&
                         bool aPreventDefault)
 {
   unused << PBrowserParent::SendKeyEvent(nsString(aType), aKeyCode, aCharCode,
                                          aModifiers, aPreventDefault);
 }
 
 bool TabParent::SendRealMouseEvent(nsMouseEvent& event)
 {
-  return PBrowserParent::SendRealMouseEvent(event);
+  nsMouseEvent e(event);
+  MaybeForwardEventToRenderFrame(event, &e);
+  return PBrowserParent::SendRealMouseEvent(e);
 }
 
 bool TabParent::SendMouseScrollEvent(nsMouseScrollEvent& event)
 {
-  return PBrowserParent::SendMouseScrollEvent(event);
+  nsMouseScrollEvent e(event);
+  MaybeForwardEventToRenderFrame(event, &e);
+  return PBrowserParent::SendMouseScrollEvent(e);
 }
 
 bool TabParent::SendRealKeyEvent(nsKeyEvent& event)
 {
-  return PBrowserParent::SendRealKeyEvent(event);
+  nsKeyEvent e(event);
+  MaybeForwardEventToRenderFrame(event, &e);
+  return PBrowserParent::SendRealKeyEvent(e);
 }
 
 bool TabParent::SendRealTouchEvent(nsTouchEvent& event)
 {
-  return PBrowserParent::SendRealTouchEvent(event);
+  nsTouchEvent e(event);
+  MaybeForwardEventToRenderFrame(event, &e);
+  return PBrowserParent::SendRealTouchEvent(e);
 }
 
 bool
 TabParent::RecvSyncMessage(const nsString& aMessage,
                            const nsString& aJSON,
                            InfallibleTArray<nsString>* aJSONRetVal)
 {
   return ReceiveMessage(aMessage, true, aJSON, aJSONRetVal);
@@ -343,20 +365,18 @@ TabParent::RecvSetCursor(const PRUint32&
     widget->SetCursor((nsCursor) aCursor);
   }
   return true;
 }
 
 bool
 TabParent::RecvSetBackgroundColor(const nscolor& aColor)
 {
-  if (nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader()) {
-    if (RenderFrameParent* frame = frameLoader->GetCurrentRemoteFrame()) {
-      frame->SetBackgroundColor(aColor);
-    }
+  if (RenderFrameParent* frame = GetRenderFrame()) {
+    frame->SetBackgroundColor(aColor);
   }
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMEFocus(const bool& aFocus,
                               nsIMEUpdatePreference* aPreference,
                               PRUint32* aSeqno)
@@ -552,16 +572,25 @@ TabParent::GetFrom(nsIContent* aContent)
   nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aContent);
   if (!loaderOwner) {
     return nsnull;
   }
   nsRefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader();
   return GetFrom(frameLoader);
 }
 
+RenderFrameParent*
+TabParent::GetRenderFrame()
+{
+  if (ManagedPRenderFrameParent().IsEmpty()) {
+    return nsnull;
+  }
+  return static_cast<RenderFrameParent*>(ManagedPRenderFrameParent()[0]);
+}
+
 bool
 TabParent::RecvEndIMEComposition(const bool& aCancel,
                                  nsString* aComposition)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return true;
 
@@ -841,22 +870,27 @@ TabParent::HandleDelayedDialogs()
   if (ShouldDelayDialogs() && mDelayedDialogs.Length()) {
     nsContentUtils::DispatchTrustedEvent(frame->OwnerDoc(), frame,
                                          NS_LITERAL_STRING("MozDelayedModalDialog"),
                                          true, true);
   }
 }
 
 PRenderFrameParent*
-TabParent::AllocPRenderFrame(LayersBackend* aBackend,
+TabParent::AllocPRenderFrame(ScrollingBehavior* aScrolling,
+                             LayersBackend* aBackend,
                              int32_t* aMaxTextureSize,
                              uint64_t* aLayersId)
 {
+  MOZ_ASSERT(ManagedPRenderFrameParent().IsEmpty());
+
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  *aScrolling = UseAsyncPanZoom() ? ASYNC_PAN_ZOOM : DEFAULT_SCROLLING;
   return new RenderFrameParent(frameLoader,
+                               *aScrolling,
                                aBackend, aMaxTextureSize, aLayersId);
 }
 
 bool
 TabParent::DeallocPRenderFrame(PRenderFrameParent* aFrame)
 {
   delete aFrame;
   return true;
@@ -956,16 +990,50 @@ TabParent::GetWidget() const
   if (!frame)
     return nsnull;
 
   nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
   return widget.forget();
 }
 
 bool
+TabParent::IsForMozBrowser()
+{
+  nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(content);
+  if (browserFrame) {
+    bool isBrowser = false;
+    browserFrame->GetReallyIsBrowser(&isBrowser);
+    return isBrowser;
+  }
+  return false;
+}
+
+bool
+TabParent::UseAsyncPanZoom()
+{
+  bool usingOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
+  bool asyncPanZoomEnabled =
+    Preferences::GetBool("layers.async-pan-zoom.enabled", false);
+  ContentParent* cp = static_cast<ContentParent*>(Manager());
+  return (usingOffMainThreadCompositing &&
+          !cp->IsForApp() && IsForMozBrowser() &&
+          asyncPanZoomEnabled);
+}
+
+void
+TabParent::MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
+                                          nsInputEvent* aOutEvent)
+{
+  if (RenderFrameParent* rfp = GetRenderFrame()) {
+    rfp->NotifyInputEvent(aEvent, aOutEvent);
+  }
+}
+
+bool
 TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                       const nsString& aURL,
                                       const nsString& aName,
                                       const nsString& aFeatures,
                                       bool* aOutWindowOpened)
 {
   *aOutWindowOpened =
     BrowserElementParent::OpenWindowOOP(static_cast<TabParent*>(aOpener),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -24,16 +24,25 @@
 struct gfxMatrix;
 struct JSContext;
 struct JSObject;
 class nsFrameLoader;
 class nsIDOMElement;
 class nsIURI;
 
 namespace mozilla {
+
+namespace layers {
+struct FrameMetrics;
+}
+
+namespace layout {
+class RenderFrameParent;
+}
+
 namespace dom {
 
 class ContentDialogParent : public PContentDialogParent {};
 
 class TabParent : public PBrowserParent 
                 , public nsITabParent 
                 , public nsIAuthPromptProvider
                 , public nsISecureBrowserUI
@@ -100,16 +109,17 @@ public:
 
 
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const nsIntSize& size);
     void UpdateDimensions(const nsRect& rect, const nsIntSize& size);
+    void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
     void Activate();
     void Deactivate();
 
     /**
      * Is this object active?  That is, was Activate() called more recently than
      * Deactivate()?
      */
     bool Active();
@@ -198,17 +208,18 @@ protected:
       nsCOMPtr<nsIDialogParamBlock> mParams;
     };
     InfallibleTArray<DelayedDialogData*> mDelayedDialogs;
 
     bool ShouldDelayDialogs();
     bool AllowContentIME();
 
     NS_OVERRIDE
-    virtual PRenderFrameParent* AllocPRenderFrame(LayersBackend* aBackend,
+    virtual PRenderFrameParent* AllocPRenderFrame(ScrollingBehavior* aScrolling,
+                                                  LayersBackend* aBackend,
                                                   int32_t* aMaxTextureSize,
                                                   uint64_t* aLayersId);
     NS_OVERRIDE
     virtual bool DeallocPRenderFrame(PRenderFrameParent* aFrame);
 
     // IME
     static TabParent *mIMETabParent;
     nsString mIMECacheText;
@@ -224,15 +235,28 @@ protected:
 
     float mDPI;
     bool mActive;
     bool mShown;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     already_AddRefed<nsIWidget> GetWidget() const;
+    layout::RenderFrameParent* GetRenderFrame();
     void TryCacheDPI();
+    // Return true iff this TabParent was created for a mozbrowser
+    // frame.
+    bool IsForMozBrowser();
+    // When true, we create a pan/zoom controller for our frame and
+    // notify it of input events targeting us.
+    bool UseAsyncPanZoom();
+    // If we have a render frame currently, notify it that we're about
+    // to dispatch |aEvent| to our child.  If there's a relevant
+    // transform in place, |aOutEvent| is the transformed |aEvent| to
+    // dispatch to content.
+    void MaybeForwardEventToRenderFrame(const nsInputEvent& aEvent,
+                                        nsInputEvent* aOutEvent);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/ipc/jar.mn
+++ b/dom/ipc/jar.mn
@@ -1,8 +1,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
         content/global/test-ipc.xul (test.xul)
         content/global/remote-test-ipc.js (remote-test.js)
-        content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
+*       content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -8,16 +8,17 @@
 
 #include "base/basictypes.h"
 
 #if defined(MOZ_WIDGET_ANDROID)
 # include <android/log.h>
 # include "AndroidBridge.h"
 #endif
 
+#include "AsyncPanZoomController.h"
 #include "BasicLayers.h"
 #include "CompositorParent.h"
 #include "LayerManagerOGL.h"
 #include "nsGkAtoms.h"
 #include "nsIWidget.h"
 #include "RenderTrace.h"
 #include "ShadowLayersParent.h"
 
@@ -29,22 +30,39 @@ namespace mozilla {
 namespace layers {
 
 // FIXME/bug 774386: we're assuming that there's only one
 // CompositorParent, but that's not always true.  This assumption only
 // affects CrossProcessCompositorParent below.
 static CompositorParent* sCurrentCompositor;
 static Thread* sCompositorThread = nsnull;
 
+struct LayerTreeState {
+  nsRefPtr<Layer> mRoot;
+  nsRefPtr<AsyncPanZoomController> mController;
+};
+
+static uint8_t sPanZoomUserDataKey;
+struct PanZoomUserData : public LayerUserData {
+  PanZoomUserData(AsyncPanZoomController* aController)
+    : mController(aController)
+  { }
+
+  // We don't keep a strong ref here because PanZoomUserData is only
+  // set transiently, and APZC is thread-safe refcounted so
+  // AddRef/Release is expensive.
+  AsyncPanZoomController* mController;
+};
+
 /**
  * Lookup the indirect shadow tree for |aId| and return it if it
  * exists.  Otherwise null is returned.  This must only be called on
  * the compositor thread.
  */
-static Layer* GetIndirectShadowTree(uint64_t aId);
+static const LayerTreeState* GetIndirectShadowTree(uint64_t aId);
 
 void CompositorParent::StartUp()
 {
   CreateCompositorMap();
   CreateThread();
 }
 
 void CompositorParent::ShutDown()
@@ -333,35 +351,41 @@ public:
   { WalkTheTree<Detach>(mRoot, nsnull); }
 
 private:
   enum Op { Resolve, Detach };
   template<Op OP>
   void WalkTheTree(Layer* aLayer, Layer* aParent)
   {
     if (RefLayer* ref = aLayer->AsRefLayer()) {
-      if (Layer* referent = GetIndirectShadowTree(ref->GetReferentId())) {
+      if (const LayerTreeState* state = GetIndirectShadowTree(ref->GetReferentId())) {
+        Layer* referent = state->mRoot;
         if (OP == Resolve) {
           ref->ConnectReferentLayer(referent);
-          TemporarilyCompensateForContentScrollOffset(ref, referent);
+          if (AsyncPanZoomController* apzc = state->mController) {
+            referent->SetUserData(&sPanZoomUserDataKey,
+                                  new PanZoomUserData(apzc));
+          } else {
+            CompensateForContentScrollOffset(ref, referent);
+          }
         } else {
           ref->DetachReferentLayer(referent);
+          referent->RemoveUserData(&sPanZoomUserDataKey);
         }
       }
     }
     for (Layer* child = aLayer->GetFirstChild();
          child; child = child->GetNextSibling()) {
       WalkTheTree<OP>(child, aLayer);
     }
   }
 
-  // FIXME/bug 750977: async pan/zoom supersedes this.  Also, the fact
-  // that we have to do this is evidence of bad API design.
-  void TemporarilyCompensateForContentScrollOffset(Layer* aContainer,
-                                                   Layer* aShadowContent)
+  // XXX the fact that we have to do this evidence of bad API design.
+  void CompensateForContentScrollOffset(Layer* aContainer,
+                                        Layer* aShadowContent)
   {
     ContainerLayer* c = aShadowContent->AsContainerLayer();
     if (!c) {
       return;
     }
     const FrameMetrics& fm = c->GetFrameMetrics();
     gfx3DMatrix m(aContainer->GetTransform());
     m.Translate(gfxPoint3D(-fm.mViewportScrollOffset.x,
@@ -386,17 +410,20 @@ CompositorParent::Composite()
 
   if (mPaused || !mLayerManager || !mLayerManager->GetRoot()) {
     return;
   }
 
   Layer* aLayer = mLayerManager->GetRoot();
   AutoResolveRefLayers resolve(aLayer);
 
-  TransformShadowTree();
+  bool requestNextFrame = TransformShadowTree(mLastCompose);
+  if (requestNextFrame) {
+    ScheduleComposition();
+  }
 
   RenderTraceLayers(aLayer, "0000");
 
   mLayerManager->EndEmptyTransaction();
 
 #ifdef COMPOSITOR_PERFORMANCE_WARNING
   if (mExpectedComposeTime + TimeDuration::FromMilliseconds(15) < TimeStamp::Now()) {
     printf_stderr("Compositor: Composite took %i ms.\n",
@@ -488,102 +515,158 @@ SetShadowProperties(Layer* aLayer)
   shadow->SetShadowClipRect(aLayer->GetClipRect());
 
   for (Layer* child = aLayer->GetFirstChild();
       child; child = child->GetNextSibling()) {
     SetShadowProperties(child);
   }
 }
 
-void
-CompositorParent::TransformShadowTree()
+bool
+CompositorParent::ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame,
+                                                   Layer *aLayer,
+                                                   bool* aWantNextFrame)
 {
+  bool appliedTransform = false;
+  for (Layer* child = aLayer->GetFirstChild();
+      child; child = child->GetNextSibling()) {
+    appliedTransform |=
+      ApplyAsyncContentTransformToTree(aCurrentFrame, child, aWantNextFrame);
+  }
+
+  ContainerLayer* container = aLayer->AsContainerLayer();
+  if (!container) {
+    return appliedTransform;
+  }
+
+  if (LayerUserData* data = aLayer->GetUserData(&sPanZoomUserDataKey)) {
+    AsyncPanZoomController* controller = static_cast<PanZoomUserData*>(data)->mController;
+    ShadowLayer* shadow = aLayer->AsShadowLayer();
+
+    gfx3DMatrix newTransform;
+    *aWantNextFrame |=
+      controller->SampleContentTransformForFrame(aCurrentFrame,
+                                                 container->GetFrameMetrics(),
+                                                 aLayer->GetTransform(),
+                                                 &newTransform);
+
+    shadow->SetShadowTransform(newTransform);
+
+    appliedTransform = true;
+  }
+
+  return appliedTransform;
+}
+
+bool
+CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame)
+{
+  bool wantNextFrame = false;
   Layer* layer = GetPrimaryScrollableLayer();
   ShadowLayer* shadow = layer->AsShadowLayer();
   ContainerLayer* container = layer->AsContainerLayer();
+  Layer* root = mLayerManager->GetRoot();
 
   const FrameMetrics& metrics = container->GetFrameMetrics();
-  const gfx3DMatrix& rootTransform = mLayerManager->GetRoot()->GetTransform();
+  const gfx3DMatrix& rootTransform = root->GetTransform();
   const gfx3DMatrix& currentTransform = layer->GetTransform();
 
-  float rootScaleX = rootTransform.GetXScale();
-  float rootScaleY = rootTransform.GetYScale();
+  // FIXME/bug 775437: unify this interface with the ~native-fennec
+  // derived code
+  // 
+  // Attempt to apply an async content transform to any layer that has
+  // an async pan zoom controller (which means that it is rendered
+  // async using Gecko). If this fails, fall back to transforming the
+  // primary scrollable layer.  "Failing" here means that we don't
+  // find a frame that is async scrollable.  Note that the fallback
+  // code also includes Fennec which is rendered async.  Fennec uses
+  // its own platform-specific async rendering that is done partially
+  // in Gecko and partially in Java.
+  if (!ApplyAsyncContentTransformToTree(aCurrentFrame, root, &wantNextFrame)) {
+    gfx3DMatrix treeTransform;
+
+    // Translate fixed position layers so that they stay in the correct position
+    // when mScrollOffset and metricsScrollOffset differ.
+    gfxPoint offset;
+    gfxPoint scaleDiff;
+
+    float rootScaleX = rootTransform.GetXScale(),
+          rootScaleY = rootTransform.GetYScale();
+
+    if (mIsFirstPaint) {
+      mContentRect = metrics.mContentRect;
+      SetFirstPaintViewport(metrics.mViewportScrollOffset,
+                            1/rootScaleX,
+                            mContentRect,
+                            metrics.mCSSContentRect);
+      mIsFirstPaint = false;
+    } else if (!metrics.mContentRect.IsEqualEdges(mContentRect)) {
+      mContentRect = metrics.mContentRect;
+      SetPageRect(metrics.mCSSContentRect);
+    }
+
+    // We synchronise the viewport information with Java after sending the above
+    // notifications, so that Java can take these into account in its response.
+    // Calculate the absolute display port to send to Java
+    nsIntRect displayPort = metrics.mDisplayPort;
+    nsIntPoint scrollOffset = metrics.mViewportScrollOffset;
+    displayPort.x += scrollOffset.x;
+    displayPort.y += scrollOffset.y;
 
-  if (mIsFirstPaint) {
-    mContentRect = metrics.mContentRect;
-    SetFirstPaintViewport(metrics.mViewportScrollOffset,
-                          1/rootScaleX,
-                          mContentRect,
-                          metrics.mCSSContentRect);
-    mIsFirstPaint = false;
-  } else if (!metrics.mContentRect.IsEqualEdges(mContentRect)) {
-    mContentRect = metrics.mContentRect;
-    SetPageRect(metrics.mCSSContentRect);
+    SyncViewportInfo(displayPort, 1/rootScaleX, mLayersUpdated,
+                     mScrollOffset, mXScale, mYScale);
+    mLayersUpdated = false;
+
+    // Handle transformations for asynchronous panning and zooming. We determine the
+    // zoom used by Gecko from the transformation set on the root layer, and we
+    // determine the scroll offset used by Gecko from the frame metrics of the
+    // primary scrollable layer. We compare this to the desired zoom and scroll
+    // offset in the view transform we obtained from Java in order to compute the
+    // transformation we need to apply.
+    float tempScaleDiffX = rootScaleX * mXScale;
+    float tempScaleDiffY = rootScaleY * mYScale;
+
+    nsIntPoint metricsScrollOffset(0, 0);
+    if (metrics.IsScrollable()) {
+      metricsScrollOffset = metrics.mViewportScrollOffset;
+    }
+
+    nsIntPoint scrollCompensation(
+      (mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
+      (mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
+    treeTransform = gfx3DMatrix(ViewTransform(-scrollCompensation, mXScale, mYScale));
+
+    // If the contents can fit entirely within the widget area on a particular
+    // dimenson, we need to translate and scale so that the fixed layers remain
+    // within the page boundaries.
+    if (mContentRect.width * tempScaleDiffX < mWidgetSize.width) {
+      offset.x = -metricsScrollOffset.x;
+      scaleDiff.x = NS_MIN(1.0f, mWidgetSize.width / (float)mContentRect.width);
+    } else {
+      offset.x = clamped(mScrollOffset.x / tempScaleDiffX, (float)mContentRect.x,
+                         mContentRect.XMost() - mWidgetSize.width / tempScaleDiffX) -
+                 metricsScrollOffset.x;
+      scaleDiff.x = tempScaleDiffX;
+    }
+
+    if (mContentRect.height * tempScaleDiffY < mWidgetSize.height) {
+      offset.y = -metricsScrollOffset.y;
+      scaleDiff.y = NS_MIN(1.0f, mWidgetSize.height / (float)mContentRect.height);
+    } else {
+      offset.y = clamped(mScrollOffset.y / tempScaleDiffY, (float)mContentRect.y,
+                         mContentRect.YMost() - mWidgetSize.height / tempScaleDiffY) -
+                 metricsScrollOffset.y;
+      scaleDiff.y = tempScaleDiffY;
+    }
+
+    shadow->SetShadowTransform(treeTransform * currentTransform);
+    TransformFixedLayers(layer, offset, scaleDiff);
   }
 
-  // We synchronise the viewport information with Java after sending the above
-  // notifications, so that Java can take these into account in its response.
-  // Calculate the absolute display port to send to Java
-  nsIntRect displayPort = metrics.mDisplayPort;
-  nsIntPoint scrollOffset = metrics.mViewportScrollOffset;
-  displayPort.x += scrollOffset.x;
-  displayPort.y += scrollOffset.y;
-
-  SyncViewportInfo(displayPort, 1/rootScaleX, mLayersUpdated,
-                   mScrollOffset, mXScale, mYScale);
-  mLayersUpdated = false;
-
-  // Handle transformations for asynchronous panning and zooming. We determine the
-  // zoom used by Gecko from the transformation set on the root layer, and we
-  // determine the scroll offset used by Gecko from the frame metrics of the
-  // primary scrollable layer. We compare this to the desired zoom and scroll
-  // offset in the view transform we obtained from Java in order to compute the
-  // transformation we need to apply.
-  float tempScaleDiffX = rootScaleX * mXScale;
-  float tempScaleDiffY = rootScaleY * mYScale;
-
-  nsIntPoint metricsScrollOffset(0, 0);
-  if (metrics.IsScrollable())
-    metricsScrollOffset = metrics.mViewportScrollOffset;
-
-  nsIntPoint scrollCompensation(
-    (mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
-    (mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
-  ViewTransform treeTransform(-scrollCompensation, mXScale, mYScale);
-  shadow->SetShadowTransform(gfx3DMatrix(treeTransform) * currentTransform);
-
-  // Translate fixed position layers so that they stay in the correct position
-  // when mScrollOffset and metricsScrollOffset differ.
-  gfxPoint offset;
-  gfxPoint scaleDiff;
-
-  // If the contents can fit entirely within the widget area on a particular
-  // dimenson, we need to translate and scale so that the fixed layers remain
-  // within the page boundaries.
-  if (mContentRect.width * tempScaleDiffX < mWidgetSize.width) {
-    offset.x = -metricsScrollOffset.x;
-    scaleDiff.x = NS_MIN(1.0f, mWidgetSize.width / (float)mContentRect.width);
-  } else {
-    offset.x = clamped(mScrollOffset.x / tempScaleDiffX, (float)mContentRect.x,
-                       mContentRect.XMost() - mWidgetSize.width / tempScaleDiffX) -
-               metricsScrollOffset.x;
-    scaleDiff.x = tempScaleDiffX;
-  }
-
-  if (mContentRect.height * tempScaleDiffY < mWidgetSize.height) {
-    offset.y = -metricsScrollOffset.y;
-    scaleDiff.y = NS_MIN(1.0f, mWidgetSize.height / (float)mContentRect.height);
-  } else {
-    offset.y = clamped(mScrollOffset.y / tempScaleDiffY, (float)mContentRect.y,
-                       mContentRect.YMost() - mWidgetSize.height / tempScaleDiffY) -
-               metricsScrollOffset.y;
-    scaleDiff.y = tempScaleDiffY;
-  }
-
-  TransformFixedLayers(layer, offset, scaleDiff);
+  return wantNextFrame;
 }
 
 void
 CompositorParent::SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom,
                                         const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect)
 {
 #ifdef MOZ_WIDGET_ANDROID
   AndroidBridge::Bridge()->SetFirstPaintViewport(aOffset, aZoom, aPageRect, aCssPageRect);
@@ -726,28 +809,67 @@ CompositorParent* CompositorParent::Remo
   CompositorMap::iterator it = sCompositorMap->find(id);
   if (it == sCompositorMap->end()) {
     return nsnull;
   }
   sCompositorMap->erase(it);
   return it->second;
 }
 
-typedef map<uint64_t, RefPtr<Layer> > LayerTreeMap;
+typedef map<uint64_t, LayerTreeState> LayerTreeMap;
 static LayerTreeMap sIndirectLayerTrees;
 
 /*static*/ uint64_t
 CompositorParent::AllocateLayerTreeId()
 {
   MOZ_ASSERT(CompositorLoop());
   MOZ_ASSERT(NS_IsMainThread());
   static uint64_t ids;
   return ++ids;
 }
 
+static void
+EraseLayerState(uint64_t aId)
+{
+  sIndirectLayerTrees.erase(aId);
+}
+
+/*static*/ void
+CompositorParent::DeallocateLayerTreeId(uint64_t aId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  CompositorLoop()->PostTask(FROM_HERE,
+                             NewRunnableFunction(&EraseLayerState, aId));
+}
+
+static void
+UpdateControllerForLayersId(uint64_t aLayersId,
+                            AsyncPanZoomController* aController)
+{
+  // Adopt ref given to us by SetPanZoomControllerForLayerTree()
+  sIndirectLayerTrees[aLayersId].mController =
+    already_AddRefed<AsyncPanZoomController>(aController);
+
+  // Notify the AsyncPanZoomController about the current compositor so that it
+  // can request composites off the compositor thread.
+  aController->SetCompositorParent(sCurrentCompositor);
+}
+
+/*static*/ void
+CompositorParent::SetPanZoomControllerForLayerTree(uint64_t aLayersId,
+                                                   AsyncPanZoomController* aController)
+{
+  // This ref is adopted by UpdateControllerForLayersId().
+  aController->AddRef();
+  CompositorLoop()->PostTask(FROM_HERE,
+                             NewRunnableFunction(&UpdateControllerForLayersId,
+                                                 aLayersId,
+                                                 aController));
+}
+
 /**
  * This class handles layer updates pushed directly from child
  * processes to the compositor thread.  It's associated with a
  * CompositorParent on the compositor thread.  While it uses the
  * PCompositor protocol to manage these updates, it doesn't actually
  * drive compositing itself.  For that it hands off work to the
  * CompositorParent it's associated with.
  */
@@ -812,30 +934,34 @@ CompositorParent::Create(Transport* aTra
     NewRunnableFunction(OpenCompositor, cpcp.get(),
                         aTransport, handle, XRE_GetIOMessageLoop()));
   // The return value is just compared to null for success checking,
   // we're not sharing a ref.
   return cpcp.get();
 }
 
 static void
-UpdateIndirectTree(uint64_t aId, Layer* aRoot)
+UpdateIndirectTree(uint64_t aId, Layer* aRoot, bool isFirstPaint)
 {
-  sIndirectLayerTrees[aId] = aRoot;
+  sIndirectLayerTrees[aId].mRoot = aRoot;
+  if (ContainerLayer* root = aRoot->AsContainerLayer()) {
+    if (AsyncPanZoomController* apzc = sIndirectLayerTrees[aId].mController) {
+      apzc->NotifyLayersUpdated(root->GetFrameMetrics(), isFirstPaint);
+    }
+  }
 }
 
-
-static Layer*
+static const LayerTreeState*
 GetIndirectShadowTree(uint64_t aId)
 {
   LayerTreeMap::const_iterator cit = sIndirectLayerTrees.find(aId);
   if (sIndirectLayerTrees.end() == cit) {
     return nsnull;
   }
-  return cit->second;
+  return &cit->second;
 }
 
 static void
 RemoveIndirectTree(uint64_t aId)
 {
   sIndirectLayerTrees.erase(aId);
 }
 
@@ -875,23 +1001,22 @@ CrossProcessCompositorParent::ShadowLaye
                                                   bool isFirstPaint)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
   Layer* shadowRoot = aLayerTree->GetRoot();
   if (shadowRoot) {
     SetShadowProperties(shadowRoot);
   }
-  UpdateIndirectTree(id, shadowRoot);
+  UpdateIndirectTree(id, shadowRoot, isFirstPaint);
 
   sCurrentCompositor->ScheduleComposition();
 }
 
 void
 CrossProcessCompositorParent::DeferredDestroy()
 {
   mSelfRef = NULL;
   // |this| was just destroyed, hands off
 }
 
 } // namespace layers
 } // namespace mozilla
-
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -25,16 +25,17 @@ class nsIWidget;
 
 namespace base {
 class Thread;
 }
 
 namespace mozilla {
 namespace layers {
 
+class AsyncPanZoomController;
 class LayerManager;
 
 // Represents (affine) transforms that are calculated from a content view.
 struct ViewTransform {
   ViewTransform(nsIntPoint aTranslation = nsIntPoint(0, 0), float aXScale = 1, float aYScale = 1)
     : mTranslation(aTranslation)
     , mXScale(aXScale)
     , mYScale(aYScale)
@@ -101,18 +102,38 @@ public:
    */
   static void StartUp();
 
   /**
    * Destroys the compositor thread and the global compositor map.
    */
   static void ShutDown();
 
-  /** Must run on the content main thread. */
+  /**
+   * Allocate an ID that can be used to refer to a layer tree and
+   * associated resources that live only on the compositor thread.
+   *
+   * Must run on the content main thread.
+   */
   static uint64_t AllocateLayerTreeId();
+  /**
+   * Release compositor-thread resources referred to by |aID|.
+   *
+   * Must run on the content main thread.
+   */
+  static void DeallocateLayerTreeId(uint64_t aId);
+
+  /**
+   * Set aController as the pan/zoom controller for the tree referred
+   * to by aLayersId.
+   *
+   * Must run on content main thread.
+   */
+  static void SetPanZoomControllerForLayerTree(uint64_t aLayersId,
+                                               AsyncPanZoomController* aController);
 
   /**
    * A new child process has been configured to push transactions
    * directly to us.  Transport is to its thread context.
    */
   static PCompositorParent*
   Create(Transport* aTransport, ProcessId aOtherProcess);
 
@@ -130,17 +151,24 @@ protected:
                                 nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
   void SetEGLSurfaceSize(int width, int height);
 
 private:
   void PauseComposition();
   void ResumeComposition();
   void ResumeCompositionAndResize(int width, int height);
 
-  void TransformShadowTree();
+  // Sample transforms for layer trees.  Return true to request
+  // another animation frame.
+  bool TransformShadowTree(TimeStamp aCurrentFrame);
+  // Return true if an AsyncPanZoomController content transform was
+  // applied for |aLayer|.  *aWantNextFrame is set to true if the
+  // controller wants another animation frame.
+  bool ApplyAsyncContentTransformToTree(TimeStamp aCurrentFrame, Layer* aLayer,
+                                        bool* aWantNextFrame);
 
   inline PlatformThreadId CompositorThreadID();
 
   /**
    * Creates a global map referencing each compositor by ID.
    *
    * This map is used by the ImageBridge protocol to trigger
    * compositions without having to keep references to the 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -675,17 +675,25 @@ PresShell::PresShell()
 #endif
 #ifdef PR_LOGGING
   if (! gLog)
     gLog = PR_NewLogModule("PresShell");
 #endif
   mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
   mIsThemeSupportDisabled = false;
   mIsActive = true;
+  // FIXME/bug 735029: find a better solution to this problem
+#ifdef MOZ_JAVA_COMPOSITOR
+  // The java pan/zoom code uses this to mean approximately "request a
+  // reset of pan/zoom state" which doesn't necessarily correspond
+  // with the first paint of content.
   mIsFirstPaint = false;
+#else
+  mIsFirstPaint = true;
+#endif
   mFrozen = false;
 #ifdef DEBUG
   mPresArenaAllocCount = 0;
 #endif
   mRenderFlags = 0;
   mXResolution = 1.0;
   mYResolution = 1.0;
   mViewportOverridden = false;
--- a/layout/ipc/Makefile.in
+++ b/layout/ipc/Makefile.in
@@ -15,16 +15,17 @@ LIBXUL_LIBRARY = 1
 FORCE_STATIC_LIB = 1
 EXPORT_LIBRARY = 1
 
 EXPORTS_NAMESPACES = mozilla/layout
 
 EXPORTS_mozilla/layout = \
   RenderFrameChild.h \
   RenderFrameParent.h \
+  RenderFrameUtils.h \
   $(NULL)
 
 CPPSRCS = \
   RenderFrameChild.cpp \
   RenderFrameParent.cpp \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
--- a/layout/ipc/PRenderFrame.ipdl
+++ b/layout/ipc/PRenderFrame.ipdl
@@ -3,18 +3,16 @@
  */
 /* 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 protocol PBrowser;
 include protocol PLayers;
 
-using mozilla::layers::LayersBackend;
-
 namespace mozilla {
 namespace layout {
 
 /**
  * PRenderFrame (in the layout sense of "frame") represents one web
  * "page".  It's used to graft content processes' layer trees into
  * chrome's rendering path.  The lifetime of a PRenderFrame is tied to
  * its PresShell in the child process.
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -8,27 +8,30 @@
 #include "base/basictypes.h"
 
 #include "BasicLayers.h"
 #include "gfx3DMatrix.h"
 #include "LayerManagerOGL.h"
 #ifdef MOZ_ENABLE_D3D9_LAYER
 # include "LayerManagerD3D9.h"
 #endif //MOZ_ENABLE_D3D9_LAYER
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ShadowLayersParent.h"
 #include "nsContentUtils.h"
 #include "nsFrameLoader.h"
 #include "nsIObserver.h"
 #include "nsSubDocumentFrame.h"
 #include "nsViewportFrame.h"
 #include "RenderFrameParent.h"
 #include "LayersBackend.h"
 
 typedef nsContentView::ViewConfig ViewConfig;
+using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 namespace mozilla {
 namespace layout {
 
 typedef FrameMetrics::ViewID ViewID;
 typedef RenderFrameParent::ViewMap ViewMap;
 
@@ -448,18 +451,50 @@ BuildBackgroundPatternFor(ContainerLayer
 
 already_AddRefed<LayerManager>
 GetFrom(nsFrameLoader* aFrameLoader)
 {
   nsIDocument* doc = aFrameLoader->GetOwnerDoc();
   return nsContentUtils::LayerManagerForDocument(doc);
 }
 
+class RemoteContentController : public GeckoContentController {
+public:
+  RemoteContentController(RenderFrameParent* aRenderFrame)
+    : mUILoop(MessageLoop::current())
+    , mRenderFrame(aRenderFrame)
+  { }
+
+  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE
+  {
+    if (MessageLoop::current() != mUILoop) {
+      // We have to send this message from the "UI thread" (main
+      // thread).
+      mUILoop->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(this, &RemoteContentController::RequestContentRepaint,
+                          aFrameMetrics));
+      return;
+    }
+    if (mRenderFrame) {
+      TabParent* browser = static_cast<TabParent*>(mRenderFrame->Manager());
+      browser->UpdateFrame(aFrameMetrics);
+    }
+  }
+
+  void ClearRenderFrame() { mRenderFrame = nsnull; }
+
+private:
+  MessageLoop* mUILoop;
+  RenderFrameParent* mRenderFrame;
+};
+
 RenderFrameParent::RenderFrameParent(nsFrameLoader* aFrameLoader,
-                                     mozilla::layers::LayersBackend* aBackendType,
+                                     ScrollingBehavior aScrollingBehavior,
+                                     LayersBackend* aBackendType,
                                      int* aMaxTextureSize,
                                      uint64_t* aId)
   : mLayersId(0)
   , mFrameLoader(aFrameLoader)
   , mFrameLoaderDestroyed(false)
   , mBackgroundColor(gfxRGBA(1, 1, 1))
 {
   mContentViews[FrameMetrics::ROOT_SCROLL_ID] =
@@ -472,16 +507,23 @@ RenderFrameParent::RenderFrameParent(nsF
   nsRefPtr<LayerManager> lm = GetFrom(mFrameLoader);
   *aBackendType = lm->GetBackendType();
   *aMaxTextureSize = lm->GetMaxTextureSize();
 
   if (CompositorParent::CompositorLoop()) {
     // Our remote frame will push layers updates to the compositor,
     // and we'll keep an indirect reference to that tree.
     *aId = mLayersId = CompositorParent::AllocateLayerTreeId();
+    if (aScrollingBehavior == ASYNC_PAN_ZOOM) {
+      mContentController = new RemoteContentController(this);
+      mPanZoomController = new AsyncPanZoomController(
+        mContentController, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+      CompositorParent::SetPanZoomControllerForLayerTree(mLayersId,
+                                                         mPanZoomController);
+    }
   }
 }
 
 RenderFrameParent::~RenderFrameParent()
 {}
 
 void
 RenderFrameParent::Destroy()
@@ -611,18 +653,44 @@ void
 RenderFrameParent::OwnerContentChanged(nsIContent* aContent)
 {
   NS_ABORT_IF_FALSE(mFrameLoader->GetOwnerContent() == aContent,
                     "Don't build new map if owner is same!");
   BuildViewMap();
 }
 
 void
+RenderFrameParent::NotifyInputEvent(const nsInputEvent& aEvent,
+                                    nsInputEvent* aOutEvent)
+{
+  if (mPanZoomController) {
+    mPanZoomController->HandleInputEvent(aEvent, aOutEvent);
+  }
+}
+
+void
+RenderFrameParent::NotifyDimensionsChanged(int width, int height)
+{
+  if (mPanZoomController) {
+    mPanZoomController->UpdateViewportSize(width, height);
+  }
+}
+
+void
 RenderFrameParent::ActorDestroy(ActorDestroyReason why)
 {
+  if (mLayersId != 0) {
+    CompositorParent::DeallocateLayerTreeId(mLayersId);
+    if (mContentController) {
+      // Stop our content controller from requesting repaints of our
+      // content.
+      mContentController->ClearRenderFrame();
+    }
+  }
+
   if (mFrameLoader && mFrameLoader->GetCurrentRemoteFrame() == this) {
     // XXX this might cause some weird issues ... we'll just not
     // redraw the part of the window covered by this until the "next"
     // remote frame has a layer-tree transaction.  For
     // why==NormalShutdown, we'll definitely want to do something
     // better, especially as nothing guarantees another Update() from
     // the "next" remote layer tree.
     mFrameLoader->SetCurrentRemoteFrame(nsnull);
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -3,49 +3,62 @@
  */
 /* 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_layout_RenderFrameParent_h
 #define mozilla_layout_RenderFrameParent_h
 
+#include <map>
+
+#include "LayersBackend.h"
 #include "mozilla/layout/PRenderFrameParent.h"
 #include "mozilla/layers/ShadowLayersManager.h"
-
-#include <map>
 #include "nsDisplayList.h"
-#include "LayersBackend.h"
+#include "RenderFrameUtils.h"
 
 class nsContentView;
 class nsFrameLoader;
 class nsSubDocumentFrame;
 
 namespace mozilla {
 
+class InputEvent;
+
 namespace layers {
+class AsyncPanZoomController;
+class GestureEventListener;
 class ShadowLayersParent;
 }
 
 namespace layout {
 
+class RemoteContentController;
+
 class RenderFrameParent : public PRenderFrameParent,
                           public mozilla::layers::ShadowLayersManager
 {
   typedef mozilla::layers::FrameMetrics FrameMetrics;
   typedef mozilla::layers::ContainerLayer ContainerLayer;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::layers::ShadowLayersParent ShadowLayersParent;
   typedef FrameMetrics::ViewID ViewID;
 
 public:
   typedef std::map<ViewID, nsRefPtr<nsContentView> > ViewMap;
 
+  /**
+   * Select the desired scrolling behavior.  If ASYNC_PAN_ZOOM is
+   * chosen, then RenderFrameParent will watch input events and use
+   * them to asynchronously pan and zoom.
+   */
   RenderFrameParent(nsFrameLoader* aFrameLoader,
+                    ScrollingBehavior aScrollingBehavior,
                     mozilla::layers::LayersBackend* aBackendType,
                     int* aMaxTextureSize,
                     uint64_t* aId);
   virtual ~RenderFrameParent();
 
   void Destroy();
 
   /**
@@ -68,39 +81,50 @@ public:
                                      nsIFrame* aFrame,
                                      LayerManager* aManager,
                                      const nsIntRect& aVisibleRect);
 
   void OwnerContentChanged(nsIContent* aContent);
 
   void SetBackgroundColor(nscolor aColor) { mBackgroundColor = gfxRGBA(aColor); };
 
+  void NotifyInputEvent(const nsInputEvent& aEvent,
+                        nsInputEvent* aOutEvent);
+
+  void NotifyDimensionsChanged(int width, int height);
+
 protected:
   void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   virtual bool RecvNotifyCompositorTransaction() MOZ_OVERRIDE;
 
   virtual PLayersParent* AllocPLayers() MOZ_OVERRIDE;
   virtual bool DeallocPLayers(PLayersParent* aLayers) MOZ_OVERRIDE;
 
 private:
   void BuildViewMap();
   void TriggerRepaint();
+  void DispatchEventForPanZoomController(const InputEvent& aEvent);
 
   ShadowLayersParent* GetShadowLayers() const;
   uint64_t GetLayerTreeId() const;
   ContainerLayer* GetRootLayer() const;
 
   // When our child frame is pushing transactions directly to the
   // compositor, this is the ID of its layer tree in the compositor's
   // context.
   uint64_t mLayersId;
 
   nsRefPtr<nsFrameLoader> mFrameLoader;
   nsRefPtr<ContainerLayer> mContainer;
+  // When our scrolling behavior is ASYNC_PAN_ZOOM, we have a nonnull
+  // AsyncPanZoomController.  It's associated with the shadow layer
+  // tree on the compositor thread.
+  nsRefPtr<layers::AsyncPanZoomController> mPanZoomController;
+  nsRefPtr<RemoteContentController> mContentController;
 
   // This contains the views for all the scrollable frames currently in the
   // painted region of our remote content.
   ViewMap mContentViews;
 
   // True after Destroy() has been called, which is triggered
   // originally by nsFrameLoader::Destroy().  After this point, we can
   // no longer safely ask the frame loader to find its nearest layer
new file mode 100644
--- /dev/null
+++ b/layout/ipc/RenderFrameUtils.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layer_RenderFrameUtils_h
+#define mozilla_layer_RenderFrameUtils_h
+
+namespace mozilla {
+namespace layout {
+
+enum ScrollingBehavior {
+  /**
+   * Use default scrolling behavior, which is synchronous: web content
+   * is reflowed and repainted for every scroll or zoom.
+   */
+  DEFAULT_SCROLLING,
+  /**
+   * Use asynchronous panning and zooming, in which prerendered
+   * content can be translated and scaled independently of the thread
+   * painting content, without content reflowing or repainting.
+   */
+  ASYNC_PAN_ZOOM,
+  SCROLLING_BEHAVIOR_SENTINEL
+};
+
+} // namespace layout
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::layout::ScrollingBehavior>
+  : public EnumSerializer<mozilla::layout::ScrollingBehavior,
+                          mozilla::layout::DEFAULT_SCROLLING,
+                          mozilla::layout::SCROLLING_BEHAVIOR_SENTINEL>
+{};
+
+} // namespace IPC
+
+#endif // mozilla_layer_RenderFrameUtils_h