Bug 746502: Add support for <meta name=viewport> on B2G/async panning and zooming r=cjones,smaug
authorDoug Sherk <dsherk2@mozilla.com>
Fri, 28 Sep 2012 22:18:18 -0400
changeset 108700 1bf3d3d3cbe0a30567cfe7475ce9863b21c46ecf
parent 108699 eaa977ba917636c02d6c86dda0282a69810b314f
child 108701 e88f05f326185447e13bf00b00f0214e2f8550b9
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerscjones, smaug
bugs746502
milestone18.0a1
Bug 746502: Add support for <meta name=viewport> on B2G/async panning and zooming r=cjones,smaug
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
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
layout/ipc/RenderFrameParent.cpp
layout/ipc/RenderFrameParent.h
--- a/dom/browser-element/BrowserElementScrolling.js
+++ b/dom/browser-element/BrowserElementScrolling.js
@@ -215,38 +215,16 @@ const ContentPanning = {
     this._zoom = metrics.zoom;
     this._viewport = new Rect(x, y,
                               compositionWidth / metrics.zoom,
                               compositionHeight / metrics.zoom);
     this._cssPageRect = new Rect(metrics.cssPageRect.x,
                                  metrics.cssPageRect.y,
                                  metrics.cssPageRect.width,
                                  metrics.cssPageRect.height);
-
-    let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-    if (this._compositionWidth != compositionWidth || this._compositionHeight != compositionHeight) {
-      cwu.setCSSViewport(compositionWidth, compositionHeight);
-      this._compositionWidth = compositionWidth;
-      this._compositionHeight = compositionHeight;
-    }
-
-    // Set scroll position
-    cwu.setScrollPositionClampingScrollPortSize(
-      compositionWidth / metrics.zoom, compositionHeight / metrics.zoom);
-    content.scrollTo(x, y);
-    cwu.setResolution(displayPort.resolution, displayPort.resolution);
-
-    let element = null;
-    if (content.document && (element = content.document.documentElement)) {
-      cwu.setDisplayPortForElement(displayPort.x,
-                                   displayPort.y,
-                                   displayPort.width,
-                                   displayPort.height,
-                                   element);
-    }
   },
 
   _recvDoubleTap: function(data) {
     let data = data.json;
 
     // We haven't received a metrics update yet; don't do anything.
     if (this._viewport == null) {
       return;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -261,16 +261,22 @@ parent:
      * We know for sure that content has either preventDefaulted or not
      * preventDefaulted. This applies to an entire batch of touch events. It is
      * expected that, if there are any DOM touch listeners, touch events will be
      * batched and only processed for panning and zooming if content does not
      * preventDefault.
      */
     ContentReceivedTouch(bool aPreventDefault);
 
+    /**
+     * Updates any zoom constraints on the parent and anything tied to it. This
+     * is useful for control logic that resides outside of the remote browser.
+     */
+    UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom);
+
     __delete__();
 
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -13,31 +13,34 @@
 #include "ContentChild.h"
 #include "IndexedDBChild.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/IntentionalCrash.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/PContentChild.h"
 #include "mozilla/dom/PContentDialogChild.h"
 #include "mozilla/ipc/DocumentRendererChild.h"
+#include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/PLayersChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozIApplication.h"
 #include "nsComponentManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsEmbedCID.h"
 #include "nsEventListenerManager.h"
+#include "nsGenericElement.h"
 #include "nsIAppsService.h"
 #include "nsIBaseWindow.h"
 #include "nsIComponentManager.h"
 #include "nsIDOMClassInfo.h"
+#include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIJSContextStack.h"
@@ -45,16 +48,18 @@
 #include "nsISSLStatusProvider.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIServiceManager.h"
 #include "nsISupportsImpl.h"
 #include "nsIURI.h"
+#include "nsIURIFixup.h"
+#include "nsCDefaultURIFixup.h"
 #include "nsIView.h"
 #include "nsIWebBrowser.h"
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXPCSecurityManager.h"
 #include "nsInterfaceHashtable.h"
 #include "nsPIDOMWindow.h"
@@ -80,16 +85,22 @@ using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::widget;
 
 NS_IMPL_ISUPPORTS1(ContentListener, nsIDOMEventListener)
 
+static const nsIntSize kDefaultViewportSize(980, 480);
+
+static const char CANCEL_DEFAULT_PAN_ZOOM[] = "cancel-default-pan-zoom";
+static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect";
+static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
+
 NS_IMETHODIMP
 ContentListener::HandleEvent(nsIDOMEvent* aEvent)
 {
   RemoteDOMEvent remoteEvent;
   remoteEvent.mEvent = do_QueryInterface(aEvent);
   NS_ENSURE_STATE(remoteEvent.mEvent);
   mTabChild->SendEvent(remoteEvent);
   return NS_OK;
@@ -146,52 +157,320 @@ TabChild::Create(uint32_t aChromeFlags,
 
 
 TabChild::TabChild(uint32_t aChromeFlags, bool aIsBrowserElement,
                    uint32_t aAppId)
   : mRemoteFrame(nullptr)
   , mTabChildGlobal(nullptr)
   , mChromeFlags(aChromeFlags)
   , mOuterRect(0, 0, 0, 0)
+  , mInnerSize(0, 0)
+  , mOldViewportWidth(0.0f)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mAppId(aAppId)
   , mDidFakeShow(false)
   , mIsBrowserElement(aIsBrowserElement)
   , mNotified(false)
+  , mContentDocumentIsDisplayed(false)
   , mTriedBrowserInit(false)
 {
     printf("creating %d!\n", NS_IsMainThread());
 }
 
-nsresult
+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();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TabChild::Observe(nsISupports *aSubject,
                   const char *aTopic,
                   const PRUnichar *aData)
 {
-  if (!strcmp(aTopic, "cancel-default-pan-zoom")) {
+  if (!strcmp(aTopic, CANCEL_DEFAULT_PAN_ZOOM)) {
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
     nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
     if (tabChild == this) {
       mRemoteFrame->CancelDefaultPanZoom();
     }
-  } else if (!strcmp(aTopic, "browser-zoom-to-rect")) {
+  } else if (!strcmp(aTopic, BROWSER_ZOOM_TO_RECT)) {
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
     nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
     if (tabChild == this) {
       gfxRect rect;
       sscanf(NS_ConvertUTF16toUTF8(aData).get(),
              "{\"x\":%lf,\"y\":%lf,\"w\":%lf,\"h\":%lf}",
              &rect.x, &rect.y, &rect.width, &rect.height);
       SendZoomToRect(rect);
     }
+  } else if (!strcmp(aTopic, BEFORE_FIRST_PAINT)) {
+    if (IsAsyncPanZoomEnabled()) {
+      nsCOMPtr<nsIDocument> subject(do_QueryInterface(aSubject));
+      nsCOMPtr<nsIDOMDocument> domDoc;
+      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.
+        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;
+        mLastMetrics.mScrollOffset = gfx::Point(0, 0);
+        utils->SetResolution(resolution, resolution);
+
+        HandlePossibleMetaViewportChange();
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::OnStateChange(nsIWebProgress* aWebProgress,
+                        nsIRequest* aRequest,
+                        uint32_t aStateFlags,
+                        nsresult aStatus)
+{
+  NS_NOTREACHED("not implemented in TabChild");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::OnProgressChange(nsIWebProgress* aWebProgress,
+                           nsIRequest* aRequest,
+                           int32_t aCurSelfProgress,
+                           int32_t aMaxSelfProgress,
+                           int32_t aCurTotalProgress,
+                           int32_t aMaxTotalProgress)
+{
+  NS_NOTREACHED("not implemented in TabChild");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::OnLocationChange(nsIWebProgress* aWebProgress,
+                           nsIRequest* aRequest,
+                           nsIURI *aLocation,
+                           uint32_t aFlags)
+{
+  if (!IsAsyncPanZoomEnabled()) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDOMWindow> window;
+  aWebProgress->GetDOMWindow(getter_AddRefs(window));
+  if (!window) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDOMDocument> progressDoc;
+  window->GetDocument(getter_AddRefs(progressDoc));
+  if (!progressDoc) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDOMDocument> domDoc;
+  mWebNav->GetDocument(getter_AddRefs(domDoc));
+  if (!domDoc || !SameCOMIdentity(domDoc, progressDoc)) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURIFixup> urifixup(do_GetService(NS_URIFIXUP_CONTRACTID));
+  if (!urifixup) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIURI> exposableURI;
+  urifixup->CreateExposableURI(aLocation, getter_AddRefs(exposableURI));
+  if (!exposableURI) {
+    return NS_OK;
+  }
+
+  if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) {
+    mContentDocumentIsDisplayed = false;
+  } else if (mLastURI != nullptr) {
+    bool exposableEqualsLast, exposableEqualsNew;
+    exposableURI->Equals(mLastURI.get(), &exposableEqualsLast);
+    exposableURI->Equals(aLocation, &exposableEqualsNew);
+    if (exposableEqualsLast && !exposableEqualsNew) {
+      mContentDocumentIsDisplayed = false;
+    }
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TabChild::OnStatusChange(nsIWebProgress* aWebProgress,
+                         nsIRequest* aRequest,
+                         nsresult aStatus,
+                         const PRUnichar* aMessage)
+{
+  NS_NOTREACHED("not implemented in TabChild");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TabChild::OnSecurityChange(nsIWebProgress* aWebProgress,
+                           nsIRequest* aRequest,
+                           uint32_t aState)
+{
+  NS_NOTREACHED("not implemented in TabChild");
+  return NS_OK;
+}
+
+void
+TabChild::SetCSSViewport(float aWidth, float aHeight)
+{
+  mOldViewportWidth = aWidth;
+
+  if (mContentDocumentIsDisplayed) {
+    nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
+    utils->SetCSSViewport(aWidth, aHeight);
+  }
+}
+
+void
+TabChild::HandlePossibleMetaViewportChange()
+{
+  if (!IsAsyncPanZoomEnabled()) {
+    return;
+  }
+
+  nsCOMPtr<nsIDOMDocument> domDoc;
+  mWebNav->GetDocument(getter_AddRefs(domDoc));
+  nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
+
+  nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
+
+  ViewportInfo viewportMetaData =
+    nsContentUtils::GetViewportInfo(document, mInnerSize.width, mInnerSize.height);
+  SendUpdateZoomConstraints(viewportMetaData.allowZoom,
+                            viewportMetaData.minZoom,
+                            viewportMetaData.maxZoom);
+
+  float screenW = mInnerSize.width;
+  float screenH = mInnerSize.height;
+  float viewportW = viewportMetaData.width;
+  float viewportH = viewportMetaData.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;
+  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
+  // 759678) such as requesting the page size or sending a viewport update. this
+  // code will get run again in the before-first-paint handler and that point we
+  // will run though all of it. the reason we even bother executing up to this
+  // point on the DOMMetaAdded handler is so that scripts that use
+  // window.innerWidth before they are painted have a correct value (bug
+  // 771575).
+  if (!mContentDocumentIsDisplayed) {
+    return;
+  }
+
+  float minScale = 1.0f;
+
+  nsCOMPtr<nsIDOMElement> htmlDOMElement = do_QueryInterface(document->GetHtmlElement());
+  nsCOMPtr<nsIDOMElement> bodyDOMElement = do_QueryInterface(document->GetBodyElement());
+
+  PRInt32 htmlWidth = 0, htmlHeight = 0;
+  if (htmlDOMElement) {
+    htmlDOMElement->GetScrollWidth(&htmlWidth);
+    htmlDOMElement->GetScrollHeight(&htmlHeight);
+  }
+  PRInt32 bodyWidth = 0, bodyHeight = 0;
+  if (bodyDOMElement) {
+    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);
+
+  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
+  //    viewport)
+  // 3. screen size remains constant, but CSS viewport changes (meta viewport
+  //    tag is added or removed)
+  // 4. neither screen size nor CSS viewport changes
+  //
+  // 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;
+  metrics.mDisplayPort = AsyncPanZoomController::CalculatePendingDisplayPort(
+    metrics,
+    gfx::Point(0.0f, 0.0f));
+  // Force a repaint with these metrics. This, among other things, sets the
+  // displayport, so we start with async painting.
+  RecvUpdateFrame(metrics);
+}
+
 nsresult
 TabChild::Init()
 {
   nsCOMPtr<nsIWebBrowser> webBrowser = do_CreateInstance(NS_WEBBROWSER_CONTRACTID);
   if (!webBrowser) {
     NS_ERROR("Couldn't create a nsWebBrowser?");
     return NS_ERROR_FAILURE;
   }
@@ -233,16 +512,22 @@ TabChild::Init()
   if (webBrowserSetup) {
     webBrowserSetup->SetProperty(nsIWebBrowserSetup::SETUP_ALLOW_DNS_PREFETCH,
                                  true);
   } else {
     NS_WARNING("baseWindow doesn't QI to nsIWebBrowserSetup, skipping "
                "DNS prefetching enable step.");
   }
 
+  nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mWebNav);
+  MOZ_ASSERT(docShell);
+  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
+  NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE);
+  webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION);
+
   return NS_OK;
 }
 
 void
 TabChild::SetAppBrowserConfig(bool aIsBrowserElement, uint32_t aAppId)
 {
     mIsBrowserElement = aIsBrowserElement;
     mAppId = aAppId;
@@ -261,18 +546,21 @@ TabChild::SetAppBrowserConfig(bool aIsBr
 NS_INTERFACE_MAP_BEGIN(TabChild)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
   NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+  NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
   NS_INTERFACE_MAP_ENTRY(nsITabChild)
   NS_INTERFACE_MAP_ENTRY(nsIDialogCreator)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsSupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(TabChild)
 NS_IMPL_RELEASE(TabChild)
 
 NS_IMETHODIMP
 TabChild::SetStatus(uint32_t aStatusType, const PRUnichar* aStatus)
@@ -794,23 +1082,26 @@ TabChild::RecvUpdateDimensions(const nsR
         return true;
     }
 
     mOuterRect.x = rect.x;
     mOuterRect.y = rect.y;
     mOuterRect.width = rect.width;
     mOuterRect.height = rect.height;
 
+    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();
+
     return true;
 }
 
 void
 TabChild::DispatchMessageManagerMessage(const nsAString& aMessageName,
                                         const nsACString& aJSONData)
 {
     JSAutoRequest ar(mCx);
@@ -837,16 +1128,18 @@ TabChild::DispatchMessageManagerMessage(
 
 bool
 TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
 {
     if (!mCx || !mTabChildGlobal) {
         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(", \"displayPort\" : ");
         data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mDisplayPort.x);
@@ -866,16 +1159,42 @@ TabChild::RecvUpdateFrame(const FrameMet
         data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mScrollableRect.y);
         data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mScrollableRect.width);
         data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mScrollableRect.height);
         data += nsPrintfCString(" }");
     data += nsPrintfCString(" }");
 
     DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data);
 
+    nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
+    nsCOMPtr<nsIDOMWindow> window = do_GetInterface(mWebNav);
+
+    utils->SetScrollPositionClampingScrollPortSize(
+      aFrameMetrics.mCompositionBounds.width / aFrameMetrics.mZoom.width,
+      aFrameMetrics.mCompositionBounds.height / aFrameMetrics.mZoom.width);
+    window->ScrollTo(aFrameMetrics.mScrollOffset.x,
+                     aFrameMetrics.mScrollOffset.y);
+    utils->SetResolution(aFrameMetrics.mResolution.width,
+                         aFrameMetrics.mResolution.width);
+
+    nsCOMPtr<nsIDOMDocument> domDoc;
+    nsCOMPtr<nsIDOMElement> docElement;
+    mWebNav->GetDocument(getter_AddRefs(domDoc));
+    if (domDoc) {
+      domDoc->GetDocumentElement(getter_AddRefs(docElement));
+      if (docElement) {
+        utils->SetDisplayPortForElement(
+          aFrameMetrics.mDisplayPort.x, aFrameMetrics.mDisplayPort.y,
+          aFrameMetrics.mDisplayPort.width, aFrameMetrics.mDisplayPort.height,
+          docElement);
+      }
+    }
+
+    mLastMetrics = aFrameMetrics;
+
     return true;
 }
 
 bool
 TabChild::RecvHandleDoubleTap(const nsIntPoint& aPoint)
 {
     if (!mCx || !mTabChildGlobal) {
         return true;
@@ -923,18 +1242,17 @@ bool
 TabChild::RecvMouseEvent(const nsString& aType,
                          const float&    aX,
                          const float&    aY,
                          const int32_t&  aButton,
                          const int32_t&  aClickCount,
                          const int32_t&  aModifiers,
                          const bool&     aIgnoreRootScrollFrame)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav);
-  nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+  nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
   NS_ENSURE_TRUE(utils, true);
   utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
                         aIgnoreRootScrollFrame, 0, 0);
   return true;
 }
 
 bool
 TabChild::RecvRealMouseEvent(const nsMouseEvent& event)
@@ -1025,18 +1343,17 @@ TabChild::RecvRealKeyEvent(const nsKeyEv
 
 bool
 TabChild::RecvKeyEvent(const nsString& aType,
                        const int32_t& aKeyCode,
                        const int32_t& aCharCode,
                        const int32_t& aModifiers,
                        const bool& aPreventDefault)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav);
-  nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+  nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
   NS_ENSURE_TRUE(utils, true);
   bool ignored = false;
   utils->SendKeyEvent(aType, aKeyCode, aCharCode,
                       aModifiers, aPreventDefault, &ignored);
   return true;
 }
 
 bool
@@ -1276,16 +1593,23 @@ TabChild::RecvDestroy()
 {
   if (mTabChildGlobal) {
     // Let the frame scripts know the child is being closed
     nsContentUtils::AddScriptRunner(
       new UnloadScriptEvent(this, mTabChildGlobal)
     );
   }
 
+  nsCOMPtr<nsIObserverService> observerService =
+    do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+
+  observerService->RemoveObserver(this, CANCEL_DEFAULT_PAN_ZOOM);
+  observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
+  observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
+
   // XXX what other code in ~TabChild() should we be running here?
   DestroyWindow();
 
   return Send__delete__(this);
 }
 
 PRenderFrameChild*
 TabChild::AllocPRenderFrame(ScrollingBehavior* aScrolling,
@@ -1314,24 +1638,26 @@ TabChild::InitTabChildGlobal(FrameScript
     NS_ENSURE_TRUE(chromeHandler, false);
 
     nsRefPtr<TabChildGlobal> scope = new TabChildGlobal(this);
     NS_ENSURE_TRUE(scope, false);
 
     mTabChildGlobal = scope;
 
     nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsIDOMEventTarget*, scope);
-  
+
     NS_ENSURE_TRUE(InitTabChildGlobalInternal(scopeSupports), false); 
 
     scope->Init();
 
     nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(chromeHandler);
     NS_ENSURE_TRUE(root, false);
     root->SetParentTarget(scope);
+
+    chromeHandler->AddEventListener(NS_LITERAL_STRING("DOMMetaAdded"), this, false);
   }
 
   if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) {
     mTriedBrowserInit = true;
     // Initialize the child side of the browser element machinery,
     // if appropriate.
     if (mIsBrowserElement || mAppId != nsIScriptSecurityManager::NO_APP_ID) {
       RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT);
@@ -1386,20 +1712,23 @@ TabChild::InitRenderingState()
 
     mRemoteFrame = remoteFrame;
 
     nsCOMPtr<nsIObserverService> observerService =
         do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
 
     if (observerService) {
         observerService->AddObserver(this,
-                                     "cancel-default-pan-zoom",
+                                     CANCEL_DEFAULT_PAN_ZOOM,
                                      false);
         observerService->AddObserver(this,
-                                     "browser-zoom-to-rect",
+                                     BROWSER_ZOOM_TO_RECT,
+                                     false);
+        observerService->AddObserver(this,
+                                     BEFORE_FIRST_PAINT,
                                      false);
     }
 
     return true;
 }
 
 void
 TabChild::SetBackgroundColor(const nscolor& aColor)
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -31,19 +31,21 @@
 #include "nsIDOMWindow.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDocument.h"
 #include "nsNetUtil.h"
 #include "nsFrameMessageManager.h"
 #include "nsIScriptContext.h"
+#include "nsIWebProgressListener.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDialogCreator.h"
 #include "nsIDialogParamBlock.h"
+#include "nsIDOMWindowUtils.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"
@@ -136,16 +138,18 @@ protected:
 
 class TabChild : public PBrowserChild,
                  public nsFrameScriptExecutor,
                  public nsIWebBrowserChrome2,
                  public nsIEmbeddingSiteWindow,
                  public nsIWebBrowserChromeFocus,
                  public nsIInterfaceRequestor,
                  public nsIWindowProvider,
+                 public nsIDOMEventListener,
+                 public nsIWebProgressListener,
                  public nsSupportsWeakReference,
                  public nsIDialogCreator,
                  public nsITabChild,
                  public nsIObserver,
                  public mozilla::dom::ipc::MessageManagerCallback
 {
     typedef mozilla::layout::RenderFrameChild RenderFrameChild;
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
@@ -170,16 +174,18 @@ public:
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIWEBBROWSERCHROME
     NS_DECL_NSIWEBBROWSERCHROME2
     NS_DECL_NSIEMBEDDINGSITEWINDOW
     NS_DECL_NSIWEBBROWSERCHROMEFOCUS
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NSIWINDOWPROVIDER
+    NS_DECL_NSIDOMEVENTLISTENER
+    NS_DECL_NSIWEBPROGRESSLISTENER
     NS_DECL_NSIDIALOGCREATOR
     NS_DECL_NSITABCHILD
     NS_DECL_NSIOBSERVER
 
     /**
      * MessageManagerCallback methods that we override.
      */
     virtual bool DoSendSyncMessage(const nsAString& aMessage,
@@ -323,16 +329,26 @@ private:
     bool InitTabChildGlobal(FrameScriptLoading aScriptLoading = DEFAULT_LOAD_SCRIPTS);
     bool InitRenderingState();
     void DestroyWindow();
     void SetProcessNameToAppName();
 
     // 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();
+
     // 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);
 
@@ -342,28 +358,40 @@ private:
     nsresult
     BrowserFrameProvideWindow(nsIDOMWindow* aOpener,
                               nsIURI* aURI,
                               const nsAString& aName,
                               const nsACString& aFeatures,
                               bool* aWindowIsNew,
                               nsIDOMWindow** aReturn);
 
+    nsIDOMWindowUtils* GetDOMWindowUtils()
+    {
+        nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav);
+        nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
+        return utils;
+    }
+
     nsCOMPtr<nsIWebNavigation> mWebNav;
     nsCOMPtr<nsIWidget> mWidget;
+    nsCOMPtr<nsIURI> mLastURI;
+    FrameMetrics mLastMetrics;
     RenderFrameChild* mRemoteFrame;
     nsRefPtr<TabChildGlobal> mTabChildGlobal;
     uint32_t mChromeFlags;
     nsIntRect mOuterRect;
+    nsIntSize mInnerSize;
+    float mOldViewportWidth;
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
     uint32_t mAppId;
     bool mDidFakeShow;
     bool mIsBrowserElement;
     bool mNotified;
+    bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 inline TabChild*
 GetTabChildFrom(nsIDocShell* aDocShell)
 {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1195,16 +1195,27 @@ TabParent::RecvZoomToRect(const gfxRect&
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ZoomToRect(aRect);
   }
   return true;
 }
 
 bool
+TabParent::RecvUpdateZoomConstraints(const bool& aAllowZoom,
+                                     const float& aMinZoom,
+                                     const float& aMaxZoom)
+{
+  if (RenderFrameParent* rfp = GetRenderFrame()) {
+    rfp->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
+  }
+  return true;
+}
+
+bool
 TabParent::RecvContentReceivedTouch(const bool& aPreventDefault)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ContentReceivedTouch(aPreventDefault);
   }
   return true;
 }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -131,16 +131,19 @@ public:
                                      const nsString& aActionHint,
                                      const int32_t& aCause,
                                      const int32_t& aFocusChange);
     virtual bool RecvSetCursor(const uint32_t& aValue);
     virtual bool RecvSetBackgroundColor(const nscolor& aValue);
     virtual bool RecvGetDPI(float* aValue);
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
     virtual bool RecvZoomToRect(const gfxRect& aRect);
+    virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom,
+                                           const float& aMinZoom,
+                                           const float& aMaxZoom);
     virtual bool RecvContentReceivedTouch(const bool& aPreventDefault);
     virtual PContentDialogParent* AllocPContentDialog(const uint32_t& aType,
                                                       const nsCString& aName,
                                                       const nsCString& aFeatures,
                                                       const InfallibleTArray<int>& aIntParams,
                                                       const InfallibleTArray<nsString>& aStringParams);
     virtual bool DeallocPContentDialog(PContentDialogParent* aDialog)
     {
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -80,16 +80,19 @@ static const double MIN_ZOOM = 0.125;
 static const int TOUCH_LISTENER_TIMEOUT = 300;
 
 AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController,
                                                GestureBehavior aGestures)
   :  mGeckoContentController(aGeckoContentController),
      mTouchListenerTimeoutTask(nullptr),
      mX(this),
      mY(this),
+     mAllowZoom(true),
+     mMinZoom(MIN_ZOOM),
+     mMaxZoom(MAX_ZOOM),
      mMonitor("AsyncPanZoomController"),
      mLastSampleTime(TimeStamp::Now()),
      mState(NOTHING),
      mDPI(72),
      mContentPainterStatus(CONTENT_IDLE),
      mDisableNextTouchBatch(false),
      mHandlingTouchQueue(false)
 {
@@ -404,23 +407,31 @@ nsEventStatus AsyncPanZoomController::On
 }
 
 nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) {
   SetState(NOTHING);
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
+  if (!mAllowZoom) {
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   SetState(PINCHING);
   mLastZoomFocus = aEvent.mFocusPoint;
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
+  if (mState != PINCHING) {
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   float prevSpan = aEvent.mPreviousSpan;
   if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
     // We're still handling it; we've just decided to throw this event away.
     return nsEventStatus_eConsumeNoDefault;
   }
 
   float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
 
@@ -442,24 +453,24 @@ nsEventStatus AsyncPanZoomController::On
     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;
 
     // Only do the scaling if we won't go over 8x zoom in or out.
-    bool doScale = (scale < MAX_ZOOM && spanRatio > 1.0f) || (scale > MIN_ZOOM && spanRatio < 1.0f);
+    bool doScale = (scale < mMaxZoom && spanRatio > 1.0f) || (scale > mMinZoom && spanRatio < 1.0f);
 
     // If this zoom will take it over 8x zoom in either direction, but it's not
     // already there, then normalize it.
-    if (scale * spanRatio > MAX_ZOOM) {
-      spanRatio = scale / MAX_ZOOM;
-    } else if (scale * spanRatio < MIN_ZOOM) {
-      spanRatio = scale / MIN_ZOOM;
+    if (scale * spanRatio > mMaxZoom) {
+      spanRatio = scale / mMaxZoom;
+    } else if (scale * spanRatio < mMinZoom) {
+      spanRatio = scale / mMinZoom;
     }
 
     if (doScale) {
       switch (mX.ScaleWillOverscroll(spanRatio, focusPoint.x))
       {
         case Axis::OVERSCROLL_NONE:
           break;
         case Axis::OVERSCROLL_MINUS:
@@ -545,20 +556,23 @@ nsEventStatus AsyncPanZoomController::On
   // XXX: Implement this.
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   if (mGeckoContentController) {
     MonitorAutoLock monitor(mMonitor);
 
-    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)));
+    if (mAllowZoom) {
+      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)));
+    }
+
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
   // XXX: Implement this.
   return nsEventStatus_eIgnore;
@@ -723,49 +737,51 @@ bool AsyncPanZoomController::EnlargeDisp
     *aDisplayPortLength = aCompositionBounds * clamped(fabsf(aVelocity),
       MIN_SKATE_SIZE_MULTIPLIER, MAX_SKATE_SIZE_MULTIPLIER);
     *aDisplayPortOffset = aVelocity > 0 ? 0 : aCompositionBounds - *aDisplayPortLength;
     return true;
   }
   return false;
 }
 
-const gfx::Rect AsyncPanZoomController::CalculatePendingDisplayPort() {
-  float scale = mFrameMetrics.mZoom.width;
-  nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
+const gfx::Rect AsyncPanZoomController::CalculatePendingDisplayPort(
+  const FrameMetrics& aFrameMetrics,
+  const gfx::Point& aVelocity)
+{
+  float scale = aFrameMetrics.mZoom.width;
+  nsIntRect compositionBounds = aFrameMetrics.mCompositionBounds;
   compositionBounds.ScaleInverseRoundIn(scale);
 
-  gfx::Point scrollOffset = mFrameMetrics.mScrollOffset;
-  gfx::Point velocity = GetVelocityVector();
+  gfx::Point scrollOffset = aFrameMetrics.mScrollOffset;
 
   const float STATIONARY_SIZE_MULTIPLIER = 2.0f;
   gfx::Rect displayPort(0, 0,
                         compositionBounds.width * STATIONARY_SIZE_MULTIPLIER,
                         compositionBounds.height * STATIONARY_SIZE_MULTIPLIER);
 
   // If there's motion along an axis of movement, and it's above a threshold,
   // then we want to paint a larger area in the direction of that motion so that
   // it's less likely to checkerboard.
   bool enlargedX = EnlargeDisplayPortAlongAxis(
-    compositionBounds.width, velocity.x, &displayPort.x, &displayPort.width);
+    compositionBounds.width, aVelocity.x, &displayPort.x, &displayPort.width);
   bool enlargedY = EnlargeDisplayPortAlongAxis(
-    compositionBounds.height, velocity.y, &displayPort.y, &displayPort.height);
+    compositionBounds.height, aVelocity.y, &displayPort.y, &displayPort.height);
 
   if (!enlargedX && !enlargedY) {
     displayPort.x = -displayPort.width / 4;
     displayPort.y = -displayPort.height / 4;
   } else if (!enlargedX) {
     displayPort.width = compositionBounds.width;
   } else if (!enlargedY) {
     displayPort.height = compositionBounds.height;
   }
 
   gfx::Rect shiftedDisplayPort = displayPort;
   shiftedDisplayPort.MoveBy(scrollOffset.x, scrollOffset.y);
-  displayPort = shiftedDisplayPort.Intersect(mFrameMetrics.mScrollableRect);
+  displayPort = shiftedDisplayPort.Intersect(aFrameMetrics.mScrollableRect);
   displayPort.MoveBy(-scrollOffset.x, -scrollOffset.y);
 
   return displayPort;
 }
 
 void AsyncPanZoomController::SetDPI(int aDPI) {
   mDPI = aDPI;
 }
@@ -776,17 +792,18 @@ int AsyncPanZoomController::GetDPI() {
 
 void AsyncPanZoomController::ScheduleComposite() {
   if (mCompositorParent) {
     mCompositorParent->ScheduleRenderOnCompositorThread();
   }
 }
 
 void AsyncPanZoomController::RequestContentRepaint() {
-  mFrameMetrics.mDisplayPort = CalculatePendingDisplayPort();
+  mFrameMetrics.mDisplayPort =
+    CalculatePendingDisplayPort(mFrameMetrics, GetVelocityVector());
 
   gfx::Point oldScrollOffset = mLastPaintRequestMetrics.mScrollOffset,
              newScrollOffset = mFrameMetrics.mScrollOffset;
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   gfx::Rect oldDisplayPort = mLastPaintRequestMetrics.mDisplayPort;
   gfx::Rect newDisplayPort = mFrameMetrics.mDisplayPort;
@@ -953,38 +970,49 @@ void AsyncPanZoomController::NotifyLayer
     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);
 
-    // Bug 776413/fixme: Request a repaint as soon as a page is loaded so that
-    // we get a larger displayport. This is very bad because we're wasting a
-    // paint and not initializating the displayport correctly.
-    RequestContentRepaint();
-
     mState = NOTHING;
   } else if (!mFrameMetrics.mScrollableRect.IsEqualEdges(aViewportFrame.mScrollableRect)) {
     mFrameMetrics.mScrollableRect = aViewportFrame.mScrollableRect;
     SetPageRect(mFrameMetrics.mScrollableRect);
   }
 }
 
 const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() {
   mMonitor.AssertCurrentThreadOwns();
   return mFrameMetrics;
 }
 
 void AsyncPanZoomController::UpdateCompositionBounds(const nsIntRect& aCompositionBounds) {
   MonitorAutoLock mon(mMonitor);
-  FrameMetrics metrics = GetFrameMetrics();
-  metrics.mCompositionBounds = aCompositionBounds;
-  mFrameMetrics = metrics;
+
+  nsIntRect oldCompositionBounds = mFrameMetrics.mCompositionBounds;
+  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);
+
+    // Repaint on a rotation so that our new resolution gets properly updated.
+    RequestContentRepaint();
+  }
 }
 
 void AsyncPanZoomController::CancelDefaultPanZoom() {
   mDisableNextTouchBatch = true;
   if (mGestureEventListener) {
     mGestureEventListener->CancelGesture();
   }
 }
@@ -1038,17 +1066,19 @@ void AsyncPanZoomController::ZoomToRect(
 
       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(mEndZoomToMetrics.mZoom.width, MIN_ZOOM, MAX_ZOOM);
+      clamped(float(mEndZoomToMetrics.mZoom.width),
+              mMinZoom,
+              mMaxZoom);
 
     // Recalculate the zoom to rect using the new dimensions.
     zoomToRect.width = compositionBounds.width / mEndZoomToMetrics.mZoom.width;
     zoomToRect.height = compositionBounds.height / mEndZoomToMetrics.mZoom.height;
 
     // Clamp the zoom to rect to the CSS rect to make sure it fits.
     zoomToRect = zoomToRect.Intersect(cssPageRect);
 
@@ -1112,10 +1142,18 @@ void AsyncPanZoomController::TimeoutTouc
 }
 
 void AsyncPanZoomController::SetZoomAndResolution(float aScale) {
   mMonitor.AssertCurrentThreadOwns();
   mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height =
   mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height = aScale;
 }
 
+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
@@ -9,16 +9,17 @@
 
 #include "GeckoContentController.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "InputData.h"
 #include "Axis.h"
+#include "nsContentUtils.h"
 
 #include "base/message_loop.h"
 
 namespace mozilla {
 namespace layers {
 
 class CompositorParent;
 class GestureEventListener;
@@ -125,16 +126,23 @@ public:
   /**
    * If we have touch listeners, this should always be called when we know
    * definitively whether or not content has preventDefaulted any touch events
    * that have come in. If |aPreventDefault| is true, any touch events in the
    * queue will be discarded.
    */
   void ContentReceivedTouch(bool aPreventDefault);
 
+  /**
+   * Updates any zoom constraints contained in the <meta name="viewport"> tag.
+   * We try to obey everything it asks us elsewhere, but here we only handle
+   * minimum-scale, maximum-scale, and user-scalable.
+   */
+  void UpdateZoomConstraints(bool aAllowZoom, float aMinScale, float aMaxScale);
+
   // --------------------------------------------------------------------------
   // These methods must only be called on the compositor thread.
   //
 
   /**
    * The compositor calls this when it's about to draw pannable/zoomable content
    * and is setting up transforms for compositing the layer tree. This is not
    * idempotent. For example, a fling transform can be applied each time this is
@@ -184,16 +192,26 @@ public:
   void SetDPI(int aDPI);
 
   /**
    * Gets the DPI of the device for use outside the panning and zooming logic.
    * It defaults to 72 if not set using SetDPI() at any point.
    */
   int GetDPI();
 
+  /**
+   * Recalculates the displayport. Ideally, this should paint an area bigger
+   * than the composite-to dimensions so that when you scroll down, you don't
+   * checkerboard immediately. This includes a bunch of logic, including
+   * algorithms to bias painting in the direction of the velocity.
+   */
+  static const gfx::Rect CalculatePendingDisplayPort(
+    const FrameMetrics& aFrameMetrics,
+    const gfx::Point& aVelocity);
+
 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
@@ -335,34 +353,27 @@ protected:
   void UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent);
 
   /**
    * Does any panning required due to a new touch event.
    */
   void TrackTouch(const MultiTouchInput& aEvent);
 
   /**
-   * Recalculates the displayport. Ideally, this should paint an area bigger
-   * than the actual screen. The viewport refers to the size of the screen,
-   * while the displayport is the area actually painted by Gecko. We paint
-   * a larger area than the screen so that when you scroll down, you don't
-   * checkerboard immediately.
-   */
-  const gfx::Rect CalculatePendingDisplayPort();
-
-  /**
    * Attempts to enlarge the displayport along a single axis. Returns whether or
    * not the displayport was enlarged. This will fail in circumstances where the
    * velocity along that axis is not high enough to need any changes. The
    * displayport metrics are expected to be passed into |aDisplayPortOffset| and
    * |aDisplayPortLength|. If enlarged, these will be updated with the new
    * metrics.
    */
-  bool EnlargeDisplayPortAlongAxis(float aCompositionBounds, float aVelocity,
-                                   float* aDisplayPortOffset, float* aDisplayPortLength);
+  static bool EnlargeDisplayPortAlongAxis(float aCompositionBounds,
+                                          float aVelocity,
+                                          float* aDisplayPortOffset,
+                                          float* aDisplayPortLength);
 
   /**
    * Utility function to send updated FrameMetrics to Gecko so that it can paint
    * the displayport area. Calls into GeckoContentController to do the actual
    * work. Note that only one paint request can be active at a time. If a paint
    * request is made while a paint is currently happening, it gets queued up. If
    * a new paint request arrives before a paint is completed, the old request
    * gets discarded.
@@ -466,20 +477,29 @@ private:
 
   nsTArray<MultiTouchInput> mTouchQueue;
 
   CancelableTask* mTouchListenerTimeoutTask;
 
   AxisX mX;
   AxisY mY;
 
-  // Protects |mFrameMetrics|, |mLastContentPaintMetrics| and |mState|. Before
-  // manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the monitor
-  // should be held. When setting |mState|, either the SetState() function can
-  // be used, or the monitor can be held and then |mState| updated.
+  // Most up-to-date constraints on zooming. These should always be reasonable
+  // values; for example, allowing a min zoom of 0.0 can cause very bad things
+  // to happen.
+  bool mAllowZoom;
+  float mMinZoom;
+  float mMaxZoom;
+
+  // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, |mState| and
+  // |mMetaViewportInfo|. Before manipulating |mFrameMetrics| or
+  // |mLastContentPaintMetrics|, the monitor should be held. When setting
+  // |mState|, either the SetState() function can be used, or the monitor can be
+  // held and then |mState| updated.  |mMetaViewportInfo| should be updated
+  // using UpdateMetaViewport().
   Monitor mMonitor;
 
   // The last time the compositor has sampled the content transform for this
   // frame.
   TimeStamp mLastSampleTime;
   // The last time a touch event came through on the UI thread.
   int32_t mLastEventTime;
 
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -908,16 +908,24 @@ RenderFrameParent::ZoomToRect(const gfxR
 void
 RenderFrameParent::ContentReceivedTouch(bool aPreventDefault)
 {
   if (mPanZoomController) {
     mPanZoomController->ContentReceivedTouch(aPreventDefault);
   }
 }
 
+void
+RenderFrameParent::UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom)
+{
+  if (mPanZoomController) {
+    mPanZoomController->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
+  }
+}
+
 }  // namespace layout
 }  // namespace mozilla
 
 already_AddRefed<Layer>
 nsDisplayRemote::BuildLayer(nsDisplayListBuilder* aBuilder,
                             LayerManager* aManager,
                             const ContainerParameters& aContainerParameters)
 {
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -96,16 +96,18 @@ public:
                         nsInputEvent* aOutEvent);
 
   void NotifyDimensionsChanged(int width, int height);
 
   void ZoomToRect(const gfxRect& aRect);
 
   void ContentReceivedTouch(bool aPreventDefault);
 
+  void UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom);
+
 protected:
   void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   virtual bool RecvNotifyCompositorTransaction() MOZ_OVERRIDE;
 
   virtual bool RecvCancelDefaultPanZoom() MOZ_OVERRIDE;
 
   virtual PLayersParent* AllocPLayers() MOZ_OVERRIDE;