Bug 795657: Integrate native viewport configuration better into async pan-zoom code. r=jwir3,roc
authorChris Jones <jones.chris.g@gmail.com>
Thu, 11 Oct 2012 22:46:24 -0700
changeset 110054 1301a72b1c394543557d3e178cd3e0896463a3d3
parent 110003 19cfc27b2c676298592f40fbcb4e5afb93bc4416
child 110055 b1775e08bb167b0d05bab35747cb7fa7d86077d7
push id16339
push useremorley@mozilla.com
push dateFri, 12 Oct 2012 11:22:28 +0000
treeherdermozilla-inbound@a68a80aabfe5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwir3, roc
bugs795657
milestone19.0a1
first release with
nightly linux32
1301a72b1c39 / 19.0a1 / 20121012030610 / files
nightly linux64
1301a72b1c39 / 19.0a1 / 20121012030610 / files
nightly mac
1301a72b1c39 / 19.0a1 / 20121012030610 / files
nightly win32
1301a72b1c39 / 19.0a1 / 20121012030610 / files
nightly win64
1301a72b1c39 / 19.0a1 / 20121012030610 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 795657: Integrate native viewport configuration better into async pan-zoom code. r=jwir3,roc This is a rollup of the following patches - Change the interpretation of FrameMetrics.mZoom to a "resolution-indepedent zoom", instead of a resolution-depedent scale factor. r=roc - Remove mention of "meta" from TabChild. r=roc - Remove some useless logging. r=roc - Tag FrameMetrics with its composition bounds at paint time. r=roc - Add a helper to calculate the render resolution for a FrameMetrics. r=roc - Add a helper to compute the approximate CSS dimensions a FrameMetrics will cover during composition. r=roc - BrowserElementScrolling doesn't actually care about zoom or resolution. r=roc - Accept the viewport that content has calculated, when it's received the latest widget geometry update. r=roc - Mechanically separate uses of zoom/resolution based on new definitions. r=roc - Convert GetViewportInfo()'s resolution-dependent scale into resolution-indepedent zoom. r=roc - Reinterpret defaultZoom == 0.0 as "intrinsic scale". r=jwir3,roc
dom/browser-element/BrowserElementScrolling.js
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
gfx/layers/FrameMetrics.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/layers/ipc/Axis.cpp
layout/base/nsDisplayList.cpp
--- a/dom/browser-element/BrowserElementScrolling.js
+++ b/dom/browser-element/BrowserElementScrolling.js
@@ -199,45 +199,35 @@ const ContentPanning = {
   },
 
   get _asyncPanZoomForViewportFrame() {
     return docShell.asyncPanZoomEnabled;
   },
 
   _recvViewportChange: function(data) {
     let metrics = data.json;
-    let displayPort = metrics.displayPort;
-
-    let compositionWidth = metrics.compositionBounds.width;
-    let compositionHeight = metrics.compositionBounds.height;
-
-    let x = metrics.x;
-    let y = metrics.y;
-
-    this._zoom = metrics.zoom;
-    this._viewport = new Rect(x, y,
-                              compositionWidth / metrics.zoom,
-                              compositionHeight / metrics.zoom);
+    this._viewport = new Rect(metrics.x, metrics.y,
+                              metrics.viewport.width,
+                              metrics.viewport.height);
     this._cssPageRect = new Rect(metrics.cssPageRect.x,
                                  metrics.cssPageRect.y,
                                  metrics.cssPageRect.width,
                                  metrics.cssPageRect.height);
   },
 
   _recvDoubleTap: function(data) {
     let data = data.json;
 
     // We haven't received a metrics update yet; don't do anything.
     if (this._viewport == null) {
       return;
     }
 
     let win = content;
 
-    let zoom = this._zoom;
     let element = ElementTouchHelper.anyElementFromPoint(win, data.x, data.y);
     if (!element) {
       this._zoomOut();
       return;
     }
 
     while (element && !this._shouldZoomToElement(element))
       element = element.parentNode;
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -178,17 +178,17 @@ TabChild::TabChild(uint32_t aChromeFlags
 NS_IMETHODIMP
 TabChild::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
   if (eventType.EqualsLiteral("DOMMetaAdded")) {
     // This meta data may or may not have been a meta viewport tag. If it was,
     // we should handle it immediately.
-    HandlePossibleMetaViewportChange();
+    HandlePossibleViewportChange();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::Observe(nsISupports *aSubject,
                   const char *aTopic,
@@ -217,31 +217,37 @@ TabChild::Observe(nsISupports *aSubject,
       mWebNav->GetDocument(getter_AddRefs(domDoc));
       nsCOMPtr<nsIDocument> doc(do_QueryInterface(domDoc));
 
       if (SameCOMIdentity(subject, doc)) {
         nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
 
         mContentDocumentIsDisplayed = true;
 
-        // Reset CSS viewport and zoom to default on new page, then calculate them
-        // properly using the actual metadata from the page.
+        // Reset CSS viewport and zoom to default on new page, then
+        // calculate them properly using the actual metadata from the
+        // page.
         SetCSSViewport(kDefaultViewportSize.width, kDefaultViewportSize.height);
 
-        // Calculate a really simple resolution that we probably won't be
-        // keeping, as well as putting the scroll offset back to the top-left of
-        // the page.
-        float resolution = float(mInnerSize.width) / float(kDefaultViewportSize.width);
-        mLastMetrics.mZoom.width = mLastMetrics.mZoom.height =
-          mLastMetrics.mResolution.width = mLastMetrics.mResolution.height =
-            resolution;
+        // Calculate a really simple resolution that we probably won't
+        // be keeping, as well as putting the scroll offset back to
+        // the top-left of the page.
+        mLastMetrics.mZoom = gfxSize(1.0, 1.0);
+        mLastMetrics.mViewport =
+            gfx::Rect(0, 0,
+                      kDefaultViewportSize.width, kDefaultViewportSize.height);
+        mLastMetrics.mCompositionBounds = nsIntRect(nsIntPoint(0, 0),
+                                                    mInnerSize);
+        mLastMetrics.mResolution =
+          AsyncPanZoomController::CalculateResolution(mLastMetrics);
         mLastMetrics.mScrollOffset = gfx::Point(0, 0);
-        utils->SetResolution(resolution, resolution);
+        utils->SetResolution(mLastMetrics.mResolution.width,
+                             mLastMetrics.mResolution.height);
 
-        HandlePossibleMetaViewportChange();
+        HandlePossibleViewportChange();
       }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -345,53 +351,53 @@ TabChild::SetCSSViewport(float aWidth, f
 
   if (mContentDocumentIsDisplayed) {
     nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
     utils->SetCSSViewport(aWidth, aHeight);
   }
 }
 
 void
-TabChild::HandlePossibleMetaViewportChange()
+TabChild::HandlePossibleViewportChange()
 {
   if (!IsAsyncPanZoomEnabled()) {
     return;
   }
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   mWebNav->GetDocument(getter_AddRefs(domDoc));
   nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
 
   nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
 
-  ViewportInfo viewportMetaData =
+  ViewportInfo viewportInfo =
     nsContentUtils::GetViewportInfo(document, mInnerSize.width, mInnerSize.height);
-  SendUpdateZoomConstraints(viewportMetaData.allowZoom,
-                            viewportMetaData.minZoom,
-                            viewportMetaData.maxZoom);
+  SendUpdateZoomConstraints(viewportInfo.allowZoom,
+                            viewportInfo.minZoom,
+                            viewportInfo.maxZoom);
 
   float screenW = mInnerSize.width;
   float screenH = mInnerSize.height;
-  float viewportW = viewportMetaData.width;
-  float viewportH = viewportMetaData.height;
+  float viewportW = viewportInfo.width;
+  float viewportH = viewportInfo.height;
 
   // We're not being displayed in any way; don't bother doing anything because
   // that will just confuse future adjustments.
   if (!screenW || !screenH) {
     return;
   }
 
   // Make sure the viewport height is not shorter than the window when the page
   // is zoomed out to show its full width. Note that before we set the viewport
   // width, the "full width" of the page isn't properly defined, so that's why
   // we have to call SetCSSViewport twice - once to set the width, and the
   // second time to figure out the height based on the layout at that width.
   float oldBrowserWidth = mOldViewportWidth;
-  mLastMetrics.mViewport.width = viewportMetaData.width;
-  mLastMetrics.mViewport.height = viewportMetaData.height;
+  mLastMetrics.mViewport.width = viewportInfo.width;
+  mLastMetrics.mViewport.height = viewportInfo.height;
   if (!oldBrowserWidth) {
     oldBrowserWidth = kDefaultViewportSize.width;
   }
   SetCSSViewport(viewportW, viewportH);
 
   // If this page has not been painted yet, then this must be getting run
   // because a meta-viewport element was added (via the DOMMetaAdded handler).
   // in this case, we should not do anything that forces a reflow (see bug
@@ -420,17 +426,17 @@ TabChild::HandlePossibleMetaViewportChan
     bodyDOMElement->GetScrollWidth(&bodyWidth);
     bodyDOMElement->GetScrollHeight(&bodyHeight);
   }
 
   float pageWidth = NS_MAX(htmlWidth, bodyWidth);
   float pageHeight = NS_MAX(htmlHeight, bodyHeight);
 
   minScale = mInnerSize.width / pageWidth;
-  minScale = clamped((double)minScale, viewportMetaData.minZoom, viewportMetaData.maxZoom);
+  minScale = clamped((double)minScale, viewportInfo.minZoom, viewportInfo.maxZoom);
 
   viewportH = NS_MAX(viewportH, screenH / minScale);
   SetCSSViewport(viewportW, viewportH);
 
   // This change to the zoom accounts for all types of changes I can conceive:
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
@@ -441,33 +447,52 @@ TabChild::HandlePossibleMetaViewportChan
   //
   // In all of these cases, we maintain how much actual content is visible
   // within the screen width. Note that "actual content" may be different with
   // respect to CSS pixels because of the CSS viewport size changing.
   int32_t oldScreenWidth = mLastMetrics.mCompositionBounds.width;
   if (!oldScreenWidth) {
     oldScreenWidth = mInnerSize.width;
   }
-  float zoomScale = (screenW * oldBrowserWidth) / (oldScreenWidth * viewportW);
-
-  float zoom = clamped(double(mLastMetrics.mZoom.width * zoomScale),
-                       viewportMetaData.minZoom, viewportMetaData.maxZoom);
-  utils->SetResolution(zoom, zoom);
 
   FrameMetrics metrics(mLastMetrics);
   metrics.mViewport = gfx::Rect(0.0f, 0.0f, viewportW, viewportH);
   metrics.mScrollableRect = gfx::Rect(0.0f, 0.0f, pageWidth, pageHeight);
   metrics.mCompositionBounds = nsIntRect(0, 0, mInnerSize.width, mInnerSize.height);
-  metrics.mZoom.width = metrics.mZoom.height =
-    metrics.mResolution.width = metrics.mResolution.height = zoom;
+
+  gfxSize intrinsicScale =
+      AsyncPanZoomController::CalculateIntrinsicScale(metrics);
+  // FIXME/bug 799585(?): GetViewportInfo() returns a defaultZoom of
+  // 0.0 to mean "did not calculate a zoom".  In that case, we default
+  // it to the intrinsic scale.
+  if (viewportInfo.defaultZoom < 0.01f) {
+    viewportInfo.defaultZoom = intrinsicScale.width;
+  }
+  MOZ_ASSERT(viewportInfo.minZoom <= viewportInfo.defaultZoom &&
+             viewportInfo.defaultZoom <= viewportInfo.maxZoom);
+  // GetViewportInfo() returns a resolution-dependent scale factor.
+  // Convert that to a resolution-indepedent zoom.
+  metrics.mZoom = gfxSize(viewportInfo.defaultZoom / intrinsicScale.width,
+                          viewportInfo.defaultZoom / intrinsicScale.height);
+
   metrics.mDisplayPort = AsyncPanZoomController::CalculatePendingDisplayPort(
     // The page must have been refreshed in some way such as a new document or
     // new CSS viewport, so we know that there's no velocity, acceleration, and
     // we have no idea how long painting will take.
     metrics, gfx::Point(0.0f, 0.0f), gfx::Point(0.0f, 0.0f), 0.0);
+  gfxSize resolution = AsyncPanZoomController::CalculateResolution(metrics);
+  // XXX is this actually hysteresis?  This calculation is not well
+  // understood.  It's taken from the previous JS implementation.
+  gfxFloat hysteresis/*?*/ =
+    gfxFloat(oldBrowserWidth) / gfxFloat(oldScreenWidth);
+  resolution.width *= hysteresis;
+  resolution.height *= hysteresis;
+  metrics.mResolution = resolution;
+  utils->SetResolution(metrics.mResolution.width, metrics.mResolution.height);
+
   // Force a repaint with these metrics. This, among other things, sets the
   // displayport, so we start with async painting.
   RecvUpdateFrame(metrics);
 }
 
 nsresult
 TabChild::Init()
 {
@@ -1072,20 +1097,16 @@ TabChild::RecvShow(const nsIntSize& size
     baseWindow->SetVisibility(true);
 
     return InitTabChildGlobal();
 }
 
 bool
 TabChild::RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size)
 {
-#ifdef DEBUG
-    printf("[TabChild] Update Dimensions to (x,y,w,h)= (%ud, %ud, %ud, %ud) and move to (w,h)= (%ud, %ud)\n", rect.x, rect.y, rect.width, rect.height, size.width, size.height);
-#endif
-
     if (!mRemoteFrame) {
         return true;
     }
 
     mOuterRect.x = rect.x;
     mOuterRect.y = rect.y;
     mOuterRect.width = rect.width;
     mOuterRect.height = rect.height;
@@ -1093,17 +1114,17 @@ TabChild::RecvUpdateDimensions(const nsR
     mInnerSize = size;
     mWidget->Resize(0, 0, size.width, size.height,
                     true);
 
     nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mWebNav);
     baseWin->SetPositionAndSize(0, 0, size.width, size.height,
                                 true);
 
-    HandlePossibleMetaViewportChange();
+    HandlePossibleViewportChange();
 
     return true;
 }
 
 void
 TabChild::DispatchMessageManagerMessage(const nsAString& aMessageName,
                                         const nsACString& aJSONData)
 {
@@ -1136,25 +1157,25 @@ TabChild::RecvUpdateFrame(const FrameMet
         return true;
     }
 
     // The BrowserElementScrolling helper must know about these updated metrics
     // for other functions it performs, such as double tap handling.
     nsCString data;
     data += nsPrintfCString("{ \"x\" : %d", NS_lround(aFrameMetrics.mScrollOffset.x));
     data += nsPrintfCString(", \"y\" : %d", NS_lround(aFrameMetrics.mScrollOffset.y));
-    // We don't treat the x and y scales any differently for this
-    // semi-platform-specific code.
-    data += nsPrintfCString(", \"zoom\" : %f", aFrameMetrics.mZoom.width);
+    data += nsPrintfCString(", \"viewport\" : ");
+        data += nsPrintfCString("{ \"width\" : %f", aFrameMetrics.mViewport.width);
+        data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mViewport.height);
+        data += nsPrintfCString(" }");
     data += nsPrintfCString(", \"displayPort\" : ");
         data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mDisplayPort.x);
         data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mDisplayPort.y);
         data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mDisplayPort.width);
         data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mDisplayPort.height);
-        data += nsPrintfCString(", \"resolution\" : %f", aFrameMetrics.mResolution.width);
         data += nsPrintfCString(" }");
     data += nsPrintfCString(", \"compositionBounds\" : ");
         data += nsPrintfCString("{ \"x\" : %d", aFrameMetrics.mCompositionBounds.x);
         data += nsPrintfCString(", \"y\" : %d", aFrameMetrics.mCompositionBounds.y);
         data += nsPrintfCString(", \"width\" : %d", aFrameMetrics.mCompositionBounds.width);
         data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mCompositionBounds.height);
         data += nsPrintfCString(" }");
     data += nsPrintfCString(", \"cssPageRect\" : ");
@@ -1165,23 +1186,25 @@ TabChild::RecvUpdateFrame(const FrameMet
         data += nsPrintfCString(" }");
     data += nsPrintfCString(" }");
 
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data);
 
     nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
     nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWebNav);
 
+    gfx::Rect cssCompositedRect =
+      AsyncPanZoomController::CalculateCompositedRectInCssPixels(aFrameMetrics);
     utils->SetScrollPositionClampingScrollPortSize(
-      aFrameMetrics.mCompositionBounds.width / aFrameMetrics.mZoom.width,
-      aFrameMetrics.mCompositionBounds.height / aFrameMetrics.mZoom.width);
+      cssCompositedRect.width, cssCompositedRect.height);
     window->ScrollTo(aFrameMetrics.mScrollOffset.x,
                      aFrameMetrics.mScrollOffset.y);
-    utils->SetResolution(aFrameMetrics.mResolution.width,
-                         aFrameMetrics.mResolution.width);
+    gfxSize resolution = AsyncPanZoomController::CalculateResolution(
+      aFrameMetrics);
+    utils->SetResolution(resolution.width, resolution.height);
 
     nsCOMPtr<nsIDOMDocument> domDoc;
     nsCOMPtr<nsIDOMElement> docElement;
     mWebNav->GetDocument(getter_AddRefs(domDoc));
     if (domDoc) {
       domDoc->GetDocumentElement(getter_AddRefs(docElement));
       if (docElement) {
         utils->SetDisplayPortForElement(
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -333,21 +333,22 @@ private:
 
     // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow().
     void DoFakeShow();
 
     // Wrapper for nsIDOMWindowUtils.setCSSViewport(). This updates some state
     // variables local to this class before setting it.
     void SetCSSViewport(float aX, float aY);
 
-    // Recalculates the display state, including the CSS viewport. This should
-    // be called whenever we believe the meta viewport data on a document may
-    // have changed. If it didn't change, this function doesn't do anything.
-    // However, it should not be called all the time as it is fairly expensive.
-    void HandlePossibleMetaViewportChange();
+    // Recalculates the display state, including the CSS
+    // viewport. This should be called whenever we believe the
+    // viewport data on a document may have changed. If it didn't
+    // change, this function doesn't do anything.  However, it should
+    // not be called all the time as it is fairly expensive.
+    void HandlePossibleViewportChange();
 
     // Wraps up a JSON object as a structured clone and sends it to the browser
     // chrome script.
     //
     // XXX/bug 780335: Do the work the browser chrome script does in C++ instead
     // so we don't need things like this.
     void DispatchMessageManagerMessage(const nsAString& aMessageName,
                                        const nsACString& aJSONData);
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -33,16 +33,17 @@ public:
     : mCompositionBounds(0, 0, 0, 0)
     , mContentRect(0, 0, 0, 0)
     , mDisplayPort(0, 0, 0, 0)
     , mViewport(0, 0, 0, 0)
     , mScrollOffset(0, 0)
     , mScrollId(NULL_SCROLL_ID)
     , mScrollableRect(0, 0, 0, 0)
     , mResolution(1, 1)
+    , mZoom(1, 1)
     , mDevPixelsPerCSSPixel(1)
     , mMayHaveTouchListeners(false)
   {}
 
   // Default copy ctor and operator= are fine
 
   bool operator==(const FrameMetrics& aOther) const
   {
@@ -199,27 +200,29 @@ public:
   //
   // Every time this frame is composited and the compositor samples its
   // transform, this metric is used to create a transform which is
   // post-multiplied into the parent's transform. Since this only happens when
   // we walk the layer tree, the resulting transform isn't stored here. Thus the
   // resolution of parent layers is opaque to this metric.
   gfxSize mResolution;
 
-  // The amount we are currently zooming the frame. This is distinct from
-  // |mResolution| as we can paint a frame at a different resolution than we
-  // zoom it at. This is useful in situations where we want to zoom a frame
-  // without forcing a repaint. At steady state, this and |mResolution| will be
-  // equal.
+  // The resolution-independent "user zoom".  For example, if a page
+  // configures the viewport to a zoom value of 2x, then this member
+  // will always be 2.0 no matter what the viewport or composition
+  // bounds.
   //
-  // Every time this frame is composited and the compositor samples its
-  // transform, this metric is used to create a transform which is
-  // post-multiplied into the parent's transform. Since this only happens when
-  // we walk the layer tree, the resulting transform isn't stored here. Thus the
-  // zoom of parent layers is opaque to this metric.
+  // In the steady state (no animations), and ignoring DPI, then the
+  // following is usually true
+  //
+  //  intrinsicScale = (mCompositionBounds / mViewport)
+  //  mResolution = mZoom * intrinsicScale
+  //
+  // When this is not true, we're probably asynchronously sampling a
+  // zoom animation for content.
   gfxSize mZoom;
 
   // The conversion factor between CSS pixels and device pixels for this frame.
   // This can vary based on a variety of things, such as reflowing-zoom. The
   // conversion factor for device pixels to layers pixels is just the
   // resolution.
   float mDevPixelsPerCSSPixel;
 
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -137,21 +137,21 @@ WidgetSpaceToCompensatedViewportSpace(co
 
   return pt;
 }
 
 nsEventStatus
 AsyncPanZoomController::ReceiveInputEvent(const nsInputEvent& aEvent,
                                           nsInputEvent* aOutEvent)
 {
-  float currentZoom;
+  gfxFloat currentResolution;
   gfx::Point currentScrollOffset, lastScrollOffset;
   {
     MonitorAutoLock monitor(mMonitor);
-    currentZoom = mFrameMetrics.mZoom.width;
+    currentResolution = CalculateResolution(mFrameMetrics).width;
     currentScrollOffset = gfx::Point(mFrameMetrics.mScrollOffset.x,
                                      mFrameMetrics.mScrollOffset.y);
     lastScrollOffset = gfx::Point(mLastContentPaintMetrics.mScrollOffset.x,
                                   mLastContentPaintMetrics.mScrollOffset.y);
   }
 
   nsEventStatus status;
   switch (aEvent.eventStructType) {
@@ -174,26 +174,26 @@ AsyncPanZoomController::ReceiveInputEven
   case NS_TOUCH_EVENT: {
     nsTouchEvent* touchEvent = static_cast<nsTouchEvent*>(aOutEvent);
     const nsTArray<nsCOMPtr<nsIDOMTouch> >& touches = touchEvent->touches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       nsIDOMTouch* touch = touches[i];
       if (touch) {
         gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
           gfx::Point(touch->mRefPoint.x, touch->mRefPoint.y),
-          currentZoom);
+          currentResolution);
         touch->mRefPoint = nsIntPoint(refPoint.x, refPoint.y);
       }
     }
     break;
   }
   default: {
     gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
       gfx::Point(aOutEvent->refPoint.x, aOutEvent->refPoint.y),
-      currentZoom);
+      currentResolution);
     aOutEvent->refPoint = nsIntPoint(refPoint.x, refPoint.y);
     break;
   }
   }
 
   return status;
 }
 
@@ -439,44 +439,46 @@ nsEventStatus AsyncPanZoomController::On
     return nsEventStatus_eConsumeNoDefault;
   }
 
   float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
 
   {
     MonitorAutoLock monitor(mMonitor);
 
-    float scale = mFrameMetrics.mZoom.width;
-
+    gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
+    gfxFloat userZoom = mFrameMetrics.mZoom.width;
     nsIntPoint focusPoint = aEvent.mFocusPoint;
-    float xFocusChange = (mLastZoomFocus.x - focusPoint.x) / scale, yFocusChange = (mLastZoomFocus.y - focusPoint.y) / scale;
+    gfxFloat xFocusChange = (mLastZoomFocus.x - focusPoint.x) / resolution;
+    gfxFloat yFocusChange = (mLastZoomFocus.y - focusPoint.y) / resolution;
     // If displacing by the change in focus point will take us off page bounds,
     // then reduce the displacement such that it doesn't.
     if (mX.DisplacementWillOverscroll(xFocusChange) != Axis::OVERSCROLL_NONE) {
       xFocusChange -= mX.DisplacementWillOverscrollAmount(xFocusChange);
     }
     if (mY.DisplacementWillOverscroll(yFocusChange) != Axis::OVERSCROLL_NONE) {
       yFocusChange -= mY.DisplacementWillOverscrollAmount(yFocusChange);
     }
     ScrollBy(gfx::Point(xFocusChange, yFocusChange));
 
     // When we zoom in with focus, we can zoom too much towards the boundaries
     // that we actually go over them. These are the needed displacements along
     // either axis such that we don't overscroll the boundaries when zooming.
-    float neededDisplacementX = 0, neededDisplacementY = 0;
+    gfxFloat neededDisplacementX = 0, neededDisplacementY = 0;
 
     // Only do the scaling if we won't go over 8x zoom in or out.
-    bool doScale = (scale < mMaxZoom && spanRatio > 1.0f) || (scale > mMinZoom && spanRatio < 1.0f);
+    bool doScale = (spanRatio > 1.0 && userZoom < mMaxZoom) ||
+                   (spanRatio < 1.0 && userZoom > mMinZoom);
 
     // If this zoom will take it over 8x zoom in either direction, but it's not
     // already there, then normalize it.
-    if (scale * spanRatio > mMaxZoom) {
-      spanRatio = scale / mMaxZoom;
-    } else if (scale * spanRatio < mMinZoom) {
-      spanRatio = scale / mMinZoom;
+    if (userZoom * spanRatio > mMaxZoom) {
+      spanRatio = userZoom / mMaxZoom;
+    } else if (userZoom * spanRatio < mMinZoom) {
+      spanRatio = userZoom / mMinZoom;
     }
 
     if (doScale) {
       switch (mX.ScaleWillOverscroll(spanRatio, focusPoint.x))
       {
         case Axis::OVERSCROLL_NONE:
           break;
         case Axis::OVERSCROLL_MINUS:
@@ -504,18 +506,17 @@ nsEventStatus AsyncPanZoomController::On
           break;
         case Axis::OVERSCROLL_BOTH:
           doScale = false;
           break;
       }
     }
 
     if (doScale) {
-      ScaleWithFocus(scale * spanRatio,
-                     focusPoint);
+      ScaleWithFocus(userZoom * spanRatio, focusPoint);
 
       if (neededDisplacementX != 0 || neededDisplacementY != 0) {
         ScrollBy(gfx::Point(neededDisplacementX, neededDisplacementY));
       }
 
       ScheduleComposite();
       // We don't want to redraw on every scale, so don't use
       // RequestContentRepaint()
@@ -544,39 +545,43 @@ nsEventStatus AsyncPanZoomController::On
   // XXX: Implement this.
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   if (mGeckoContentController) {
     MonitorAutoLock monitor(mMonitor);
 
+    gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
     gfx::Point point = WidgetSpaceToCompensatedViewportSpace(
       gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y),
-      mFrameMetrics.mZoom.width);
-    mGeckoContentController->HandleSingleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y)));
+      resolution);
+    mGeckoContentController->HandleSingleTap(nsIntPoint(NS_lround(point.x),
+                                                        NS_lround(point.y)));
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
   // XXX: Implement this.
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   if (mGeckoContentController) {
     MonitorAutoLock monitor(mMonitor);
 
     if (mAllowZoom) {
+      gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
       gfx::Point point = WidgetSpaceToCompensatedViewportSpace(
         gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y),
-        mFrameMetrics.mZoom.width);
-      mGeckoContentController->HandleDoubleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y)));
+        resolution);
+      mGeckoContentController->HandleDoubleTap(nsIntPoint(NS_lround(point.x),
+                                                          NS_lround(point.y)));
     }
 
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
@@ -638,20 +643,22 @@ void AsyncPanZoomController::TrackTouch(
 
   UpdateWithTouchAtDevicePoint(aEvent);
 
   {
     MonitorAutoLock monitor(mMonitor);
 
     // We want to inversely scale it because when you're zoomed further in, a
     // larger swipe should move you a shorter distance.
-    float inverseScale = 1 / mFrameMetrics.mZoom.width;
+    gfxFloat inverseResolution = 1 / CalculateResolution(mFrameMetrics).width;
 
-    int32_t xDisplacement = mX.GetDisplacementForDuration(inverseScale, timeDelta);
-    int32_t yDisplacement = mY.GetDisplacementForDuration(inverseScale, timeDelta);
+    int32_t xDisplacement = mX.GetDisplacementForDuration(inverseResolution,
+                                                          timeDelta);
+    int32_t yDisplacement = mY.GetDisplacementForDuration(inverseResolution,
+                                                          timeDelta);
     if (!xDisplacement && !yDisplacement) {
       return;
     }
 
     ScrollBy(gfx::Point(xDisplacement, yDisplacement));
     ScheduleComposite();
 
     RequestContentRepaint();
@@ -676,21 +683,21 @@ bool AsyncPanZoomController::DoFling(con
     SetZoomAndResolution(mFrameMetrics.mZoom.width);
     RequestContentRepaint();
     mState = NOTHING;
     return false;
   }
 
   // We want to inversely scale it because when you're zoomed further in, a
   // larger swipe should move you a shorter distance.
-  float inverseScale = 1 / mFrameMetrics.mZoom.width;
+  gfxFloat inverseResolution = 1 / CalculateResolution(mFrameMetrics).width;
 
   ScrollBy(gfx::Point(
-    mX.GetDisplacementForDuration(inverseScale, aDelta),
-    mY.GetDisplacementForDuration(inverseScale, aDelta)
+    mX.GetDisplacementForDuration(inverseResolution, aDelta),
+    mY.GetDisplacementForDuration(inverseResolution, aDelta)
   ));
   RequestContentRepaint();
 
   return true;
 }
 
 void AsyncPanZoomController::CancelAnimation() {
   mState = NOTHING;
@@ -706,43 +713,48 @@ void AsyncPanZoomController::ScrollBy(co
   FrameMetrics metrics(mFrameMetrics);
   metrics.mScrollOffset = newOffset;
   mFrameMetrics = metrics;
 }
 
 void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
   FrameMetrics metrics = mFrameMetrics;
   gfx::Rect pageSize = aCSSPageRect;
-  float scale = mFrameMetrics.mZoom.width;
+  gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
 
   // The page rect is the css page rect scaled by the current zoom.
-  pageSize.ScaleInverseRoundOut(scale);
+  pageSize.ScaleInverseRoundOut(resolution);
 
   // Round the page rect so we don't get any truncation, then get the nsIntRect
   // from this.
-  metrics.mContentRect = nsIntRect(pageSize.x, pageSize.y, pageSize.width, pageSize.height);
+  metrics.mContentRect = nsIntRect(pageSize.x, pageSize.y,
+                                   pageSize.width, pageSize.height);
   metrics.mScrollableRect = aCSSPageRect;
 
   mFrameMetrics = metrics;
 }
 
-void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFocus) {
-  float scaleFactor = aScale / mFrameMetrics.mZoom.width;
+void AsyncPanZoomController::ScaleWithFocus(float aZoom,
+                                            const nsIntPoint& aFocus) {
+  float zoomFactor = aZoom / mFrameMetrics.mZoom.width;
+  gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
 
-  SetZoomAndResolution(aScale);
+  SetZoomAndResolution(aZoom);
 
   // Force a recalculation of the page rect based on the new zoom and the
   // current CSS page rect (which is unchanged since it's not affected by zoom).
   SetPageRect(mFrameMetrics.mScrollableRect);
 
   // If the new scale is very small, we risk multiplying in huge rounding
   // errors, so don't bother adjusting the scroll offset.
-  if (aScale >= 0.01f) {
-    mFrameMetrics.mScrollOffset.x += float(aFocus.x) * (scaleFactor - 1.0f) / aScale;
-    mFrameMetrics.mScrollOffset.y += float(aFocus.y) * (scaleFactor - 1.0f) / aScale;
+  if (resolution >= 0.01f) {
+    mFrameMetrics.mScrollOffset.x +=
+      gfxFloat(aFocus.x) * (zoomFactor - 1.0) / resolution;
+    mFrameMetrics.mScrollOffset.y +=
+      gfxFloat(aFocus.y) * (zoomFactor - 1.0) / resolution;
   }
 }
 
 bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier,
                                                          double aEstimatedPaintDuration,
                                                          float aCompositionBounds,
                                                          float aVelocity,
                                                          float aAcceleration,
@@ -799,19 +811,19 @@ const gfx::Rect AsyncPanZoomController::
   // If we don't get an estimated paint duration, we probably don't have any
   // data. In this case, we're dealing with either a stationary frame or a first
   // paint. In either of these cases, we can just assume it'll take 1 second to
   // paint. Getting this correct is not important anyways since it's only really
   // useful when accelerating, which can't be happening at this point.
   double estimatedPaintDuration =
     aEstimatedPaintDuration > EPSILON ? aEstimatedPaintDuration : 1.0;
 
-  float scale = aFrameMetrics.mZoom.width;
+  gfxFloat resolution = CalculateResolution(aFrameMetrics).width;
   nsIntRect compositionBounds = aFrameMetrics.mCompositionBounds;
-  compositionBounds.ScaleInverseRoundIn(scale);
+  compositionBounds.ScaleInverseRoundIn(resolution);
   const gfx::Rect& scrollableRect = aFrameMetrics.mScrollableRect;
 
   gfx::Point scrollOffset = aFrameMetrics.mScrollOffset;
 
   gfx::Rect displayPort(0, 0,
                         compositionBounds.width * X_STATIONARY_SIZE_MULTIPLIER,
                         compositionBounds.height * Y_STATIONARY_SIZE_MULTIPLIER);
 
@@ -859,16 +871,45 @@ const gfx::Rect AsyncPanZoomController::
   gfx::Rect shiftedDisplayPort = displayPort;
   shiftedDisplayPort.MoveBy(scrollOffset.x, scrollOffset.y);
   displayPort = shiftedDisplayPort.Intersect(aFrameMetrics.mScrollableRect);
   displayPort.MoveBy(-scrollOffset.x, -scrollOffset.y);
 
   return displayPort;
 }
 
+/*static*/ gfxSize
+AsyncPanZoomController::CalculateIntrinsicScale(const FrameMetrics& aMetrics)
+{
+  gfxFloat intrinsicScale = (gfxFloat(aMetrics.mCompositionBounds.width) / 
+                             gfxFloat(aMetrics.mViewport.width));
+  return gfxSize(intrinsicScale, intrinsicScale);
+}
+
+/*static*/ gfxSize
+AsyncPanZoomController::CalculateResolution(const FrameMetrics& aMetrics)
+{
+  gfxSize intrinsicScale = CalculateIntrinsicScale(aMetrics);
+  gfxSize userZoom = aMetrics.mZoom;
+  return gfxSize(intrinsicScale.width * userZoom.width,
+                 intrinsicScale.height * userZoom.height);
+}
+
+/*static*/ gfx::Rect
+AsyncPanZoomController::CalculateCompositedRectInCssPixels(const FrameMetrics& aMetrics)
+{
+  gfxSize resolution = CalculateResolution(aMetrics);
+  gfx::Rect rect(aMetrics.mCompositionBounds.x,
+                 aMetrics.mCompositionBounds.y,
+                 aMetrics.mCompositionBounds.width,
+                 aMetrics.mCompositionBounds.height);
+  rect.ScaleInverseRoundIn(resolution.width, resolution.height);
+  return rect;
+}
+
 void AsyncPanZoomController::SetDPI(int aDPI) {
   mDPI = aDPI;
 }
 
 int AsyncPanZoomController::GetDPI() {
   return mDPI;
 }
 
@@ -911,37 +952,36 @@ void AsyncPanZoomController::RequestCont
   if (fabsf(oldDisplayPort.x - newDisplayPort.x) < EPSILON &&
       fabsf(oldDisplayPort.y - newDisplayPort.y) < EPSILON &&
       fabsf(oldDisplayPort.width - newDisplayPort.width) < EPSILON &&
       fabsf(oldDisplayPort.height - newDisplayPort.height) < EPSILON &&
       mFrameMetrics.mResolution.width == mLastPaintRequestMetrics.mResolution.width) {
     return;
   }
 
-  // Cache the resolution since we're temporarily changing it to accomodate
-  // mixed resolution/zoom (normally we make them the same thing).
-  float actualResolution = mFrameMetrics.mResolution.width;
+  // Cache the zoom since we're temporarily changing it for
+  // acceleration-scaled painting.
+  gfxFloat actualZoom = mFrameMetrics.mZoom.width;
   // Calculate the factor of acceleration based on the faster of the two axes.
   float accelerationFactor =
     clamped(NS_MAX(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()),
             float(MIN_ZOOM) / 2.0f, float(MAX_ZOOM));
   // Scale down the resolution a bit based on acceleration.
-  mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
-    actualResolution / accelerationFactor;
+  mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height =
+                              actualZoom / accelerationFactor;
 
   // This message is compressed, so fire whether or not we already have a paint
   // queued up. We need to know whether or not a paint was requested anyways,
-  // ofr the purposes of content calling window.scrollTo().
+  // for the purposes of content calling window.scrollTo().
   mGeckoContentController->RequestContentRepaint(mFrameMetrics);
   mLastPaintRequestMetrics = mFrameMetrics;
   mWaitingForContentToPaint = true;
 
-  // Set the resolution back to what it was for the purpose of logic control.
-  mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
-    actualResolution;
+  // Set the zoom back to what it was for the purpose of logic control.
+  mFrameMetrics.mZoom = gfxSize(actualZoom, actualZoom);
 }
 
 bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
                                                             ContainerLayer* aLayer,
                                                             gfx3DMatrix* aNewTransform) {
   // The eventual return value of this function. The compositor needs to know
   // whether or not to advance by a frame as soon as it can. For example, if a
   // fling is happening, it has to keep compositing so that the animation is
@@ -957,33 +997,36 @@ bool AsyncPanZoomController::SampleConte
 
   gfx::Point metricsScrollOffset(0, 0);
   gfx::Point scrollOffset;
   float localScaleX, localScaleY;
   const FrameMetrics& frame = aLayer->GetFrameMetrics();
   {
     MonitorAutoLock mon(mMonitor);
 
-    switch (mState)
-    {
+    switch (mState) {
     case FLING:
-      // If a fling is currently happening, apply it now. We can pull the updated
-      // metrics afterwards.
+      // If a fling is currently happening, apply it now. We can pull
+      // the updated metrics afterwards.
       requestAnimationFrame |= DoFling(aSampleTime - mLastSampleTime);
       break;
     case ANIMATING_ZOOM: {
       double animPosition = (aSampleTime - mAnimationStartTime) / ZOOM_TO_DURATION;
       if (animPosition > 1.0) {
         animPosition = 1.0;
       }
+      // Sample the zoom at the current time point.  The sampled zoom
+      // will affect the final computed resolution.
       double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
 
-      mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height =
-        mEndZoomToMetrics.mZoom.width * sampledPosition +
-          mStartZoomToMetrics.mZoom.width * (1 - sampledPosition);
+      gfxFloat startZoom = mStartZoomToMetrics.mZoom.width;
+      gfxFloat endZoom = mEndZoomToMetrics.mZoom.width;
+      gfxFloat sampledZoom = (endZoom * sampledPosition +
+                              startZoom * (1 - sampledPosition));
+      mFrameMetrics.mZoom = gfxSize(sampledZoom, sampledZoom);
 
       mFrameMetrics.mScrollOffset = gfx::Point(
         mEndZoomToMetrics.mScrollOffset.x * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition),
         mEndZoomToMetrics.mScrollOffset.y * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition)
       );
 
@@ -997,22 +1040,23 @@ bool AsyncPanZoomController::SampleConte
       }
 
       break;
     }
     default:
       break;
     }
 
-    // Current local transform; this is not what's painted but rather what PZC has
-    // transformed due to touches like panning or pinching. Eventually, the root
-    // layer transform will become this during runtime, but we must wait for Gecko
-    // to repaint.
-    localScaleX = mFrameMetrics.mZoom.width;
-    localScaleY = mFrameMetrics.mZoom.height;
+    // Current local transform; this is not what's painted but rather
+    // what PZC has transformed due to touches like panning or
+    // pinching. Eventually, the root layer transform will become this
+    // during runtime, but we must wait for Gecko to repaint.
+    gfxSize localScale = CalculateResolution(mFrameMetrics);
+    localScaleX = localScale.width;
+    localScaleY = localScale.height;
 
     if (frame.IsScrollable()) {
       metricsScrollOffset = frame.GetScrollOffsetInLayerPixels();
     }
 
     scrollOffset = mFrameMetrics.mScrollOffset;
   }
 
@@ -1068,38 +1112,46 @@ void AsyncPanZoomController::NotifyLayer
       break;
     // Don't clobber if we're in other states.
     default:
       break;
     }
   }
 
   mWaitingForContentToPaint = false;
+  bool needContentRepaint = false;
+  if (aViewportFrame.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width &&
+      aViewportFrame.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) {
+    // Remote content has sync'd up to the composition geometry
+    // change, so we can accept the viewport it's calculated.
+    gfxSize previousResolution = CalculateResolution(mFrameMetrics);
+    mFrameMetrics.mViewport = aViewportFrame.mViewport;
+    gfxSize newResolution = CalculateResolution(mFrameMetrics);
+    needContentRepaint |= (previousResolution != newResolution);
+  }
 
   if (aIsFirstPaint || mFrameMetrics.IsDefault()) {
     mPreviousPaintDurations.Clear();
 
     mX.CancelTouch();
     mY.CancelTouch();
 
-    // The composition bounds are not stored within the layers code, so we have
-    // to reset them back to what they were every time we overwrite them.
-    nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
     mFrameMetrics = aViewportFrame;
-    mFrameMetrics.mCompositionBounds = compositionBounds;
 
-    // On first paint, we want to bring zoom back in sync with resolution.
-    mFrameMetrics.mZoom = mFrameMetrics.mResolution;
     SetPageRect(mFrameMetrics.mScrollableRect);
 
     mState = NOTHING;
   } else if (!mFrameMetrics.mScrollableRect.IsEqualEdges(aViewportFrame.mScrollableRect)) {
     mFrameMetrics.mScrollableRect = aViewportFrame.mScrollableRect;
     SetPageRect(mFrameMetrics.mScrollableRect);
   }
+
+  if (needContentRepaint) {
+    RequestContentRepaint();
+  }
 }
 
 const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() {
   mMonitor.AssertCurrentThreadOwns();
   return mFrameMetrics;
 }
 
 void AsyncPanZoomController::UpdateCompositionBounds(const nsIntRect& aCompositionBounds) {
@@ -1109,21 +1161,17 @@ void AsyncPanZoomController::UpdateCompo
   mFrameMetrics.mCompositionBounds = aCompositionBounds;
 
   // If the window had 0 dimensions before, or does now, we don't want to
   // repaint or update the zoom since we'll run into rendering issues and/or
   // divide-by-zero. This manifests itself as the screen flashing. If the page
   // has gone out of view, the buffer will be cleared elsewhere anyways.
   if (aCompositionBounds.width && aCompositionBounds.height &&
       oldCompositionBounds.width && oldCompositionBounds.height) {
-    // Alter the zoom such that we can see the same width of the page as we used
-    // to be able to.
-    SetZoomAndResolution(mFrameMetrics.mResolution.width *
-                         aCompositionBounds.width /
-                         oldCompositionBounds.width);
+    SetZoomAndResolution(mFrameMetrics.mZoom.width);
 
     // Repaint on a rotation so that our new resolution gets properly updated.
     RequestContentRepaint();
   }
 }
 
 void AsyncPanZoomController::CancelDefaultPanZoom() {
   mDisableNextTouchBatch = true;
@@ -1138,23 +1186,25 @@ void AsyncPanZoomController::ZoomToRect(
   SetState(ANIMATING_ZOOM);
 
   {
     MonitorAutoLock mon(mMonitor);
 
     nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
     gfx::Rect cssPageRect = mFrameMetrics.mScrollableRect;
     gfx::Point scrollOffset = mFrameMetrics.mScrollOffset;
+    gfxSize resolution = CalculateResolution(mFrameMetrics);
 
     // If the rect is empty, treat it as a request to zoom out to the full page
     // size.
     if (zoomToRect.IsEmpty()) {
       // composition bounds in CSS coordinates
       nsIntRect cssCompositionBounds = compositionBounds;
-      cssCompositionBounds.ScaleInverseRoundIn(mFrameMetrics.mZoom.width);
+      cssCompositionBounds.ScaleInverseRoundIn(resolution.width,
+                                               resolution.height);
       cssCompositionBounds.MoveBy(scrollOffset.x, scrollOffset.y);
 
       float y = mFrameMetrics.mScrollOffset.y;
       float newHeight =
         cssCompositionBounds.height * cssPageRect.width / cssCompositionBounds.width;
       float dh = cssCompositionBounds.height - newHeight;
 
       zoomToRect = gfx::Rect(0.0f,
@@ -1177,34 +1227,35 @@ void AsyncPanZoomController::ZoomToRect(
         float newWidth = targetRatio * zoomToRect.width;
         zoomToRect.x -= (newWidth - zoomToRect.width) / 2;
         zoomToRect.width = newWidth;
       }
 
       zoomToRect = zoomToRect.Intersect(cssPageRect);
     }
 
-    mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height =
-      NS_MIN(compositionBounds.width / zoomToRect.width, compositionBounds.height / zoomToRect.height);
-
-    mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height =
-      clamped(float(mEndZoomToMetrics.mZoom.width),
-              mMinZoom,
-              mMaxZoom);
+    gfxFloat targetResolution =
+      NS_MIN(compositionBounds.width / zoomToRect.width,
+             compositionBounds.height / zoomToRect.height);
+    gfxFloat targetZoom = clamped(float(targetResolution / resolution.width),
+                                  mMinZoom, mMaxZoom);
+    mEndZoomToMetrics.mZoom = gfxSize(targetZoom, targetZoom);
 
     // Recalculate the zoom to rect using the new dimensions.
-    zoomToRect.width = compositionBounds.width / mEndZoomToMetrics.mZoom.width;
-    zoomToRect.height = compositionBounds.height / mEndZoomToMetrics.mZoom.height;
+    zoomToRect.width = compositionBounds.width / targetResolution;
+    zoomToRect.height = compositionBounds.height / targetResolution;
 
     // Clamp the zoom to rect to the CSS rect to make sure it fits.
     zoomToRect = zoomToRect.Intersect(cssPageRect);
 
     // Do one final recalculation to get the resolution.
-    mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height =
-      NS_MAX(compositionBounds.width / zoomToRect.width, compositionBounds.height / zoomToRect.height);
+    targetResolution = NS_MAX(compositionBounds.width / zoomToRect.width,
+                              compositionBounds.height / zoomToRect.height);
+    targetZoom = targetResolution / resolution.width;
+    mEndZoomToMetrics.mZoom = gfxSize(targetZoom, targetZoom);
 
     mStartZoomToMetrics = mFrameMetrics;
     mEndZoomToMetrics.mScrollOffset =
       gfx::Point(zoomToRect.x, zoomToRect.y);
 
     mAnimationStartTime = TimeStamp::Now();
 
     ScheduleComposite();
@@ -1251,20 +1302,20 @@ void AsyncPanZoomController::SetState(Pa
   MonitorAutoLock monitor(mMonitor);
   mState = aState;
 }
 
 void AsyncPanZoomController::TimeoutTouchListeners() {
   ContentReceivedTouch(false);
 }
 
-void AsyncPanZoomController::SetZoomAndResolution(float aScale) {
+void AsyncPanZoomController::SetZoomAndResolution(float aZoom) {
   mMonitor.AssertCurrentThreadOwns();
-  mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
-  mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height = aScale;
+  mFrameMetrics.mZoom = gfxSize(aZoom, aZoom);
+  mFrameMetrics.mResolution = CalculateResolution(mFrameMetrics);
 }
 
 void AsyncPanZoomController::UpdateZoomConstraints(bool aAllowZoom,
                                                    float aMinZoom,
                                                    float aMaxZoom) {
   mAllowZoom = aAllowZoom;
   mMinZoom = aMinZoom;
   mMaxZoom = aMaxZoom;
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -204,16 +204,32 @@ public:
    * algorithms to bias painting in the direction of the velocity.
    */
   static const gfx::Rect CalculatePendingDisplayPort(
     const FrameMetrics& aFrameMetrics,
     const gfx::Point& aVelocity,
     const gfx::Point& aAcceleration,
     double aEstimatedPaintDuration);
 
+  /**
+   * Return the scale factor needed to fit the viewport in |aMetrics|
+   * into its compositiong bounds.
+   */
+  static gfxSize CalculateIntrinsicScale(const FrameMetrics& aMetrics);
+
+  /**
+   * Return the resolution that content should be rendered at given
+   * the configuration in aFrameMetrics: viewport dimensions, zoom
+   * factor, etc.  (The mResolution member of aFrameMetrics is
+   * ignored.)
+   */
+  static gfxSize CalculateResolution(const FrameMetrics& aMetrics);
+
+  static gfx::Rect CalculateCompositedRectInCssPixels(const FrameMetrics& aMetrics);
+
 protected:
   /**
    * Internal handler for ReceiveInputEvent(). Does all the actual work.
    */
   nsEventStatus HandleInputEvent(const InputData& aEvent);
 
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -248,24 +248,20 @@ float Axis::GetPageEnd() {
 }
 
 float Axis::GetOrigin() {
   gfx::Point origin = mAsyncPanZoomController->GetFrameMetrics().mScrollOffset;
   return GetPointOffset(origin);
 }
 
 float Axis::GetCompositionLength() {
-  nsIntRect compositionBounds =
-    mAsyncPanZoomController->GetFrameMetrics().mCompositionBounds;
-  gfx::Rect scaledCompositionBounds =
-    gfx::Rect(compositionBounds.x, compositionBounds.y,
-              compositionBounds.width, compositionBounds.height);
-  scaledCompositionBounds.ScaleInverseRoundIn(
-    mAsyncPanZoomController->GetFrameMetrics().mZoom.width);
-  return GetRectLength(scaledCompositionBounds);
+  const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
+  gfx::Rect cssCompositedRect =
+    AsyncPanZoomController::CalculateCompositedRectInCssPixels(metrics);
+  return GetRectLength(cssCompositedRect);
 }
 
 float Axis::GetPageStart() {
   gfx::Rect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect;
   return GetRectOffset(pageRect);
 }
 
 float Axis::GetPageLength() {
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -632,16 +632,20 @@ static void RecordFrameMetrics(nsIFrame*
 
   nsIPresShell* presShell = presContext->GetPresShell();
   metrics.mResolution = gfxSize(presShell->GetXResolution(), presShell->GetYResolution());
 
   metrics.mDevPixelsPerCSSPixel = auPerCSSPixel / auPerDevPixel;
 
   metrics.mMayHaveTouchListeners = aMayHaveTouchListeners;
 
+  if (nsIWidget* widget = aForFrame->GetNearestWidget()) {
+    widget->GetBounds(metrics.mCompositionBounds);
+  }
+
   aRoot->SetFrameMetrics(metrics);
 }
 
 nsDisplayListBuilder::~nsDisplayListBuilder() {
   NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
                "All frames should have been unmarked");
   NS_ASSERTION(mPresShellStates.Length() == 0,
                "All presshells should have been exited");