Bug 1076241 - Add an API for setting a resolution on a document and scaling it by that amount. r=kats,tn
authorBotond Ballo <botond@mozilla.com>
Fri, 02 Jan 2015 20:06:14 -0500
changeset 248340 42272b7f8e4810be997951082901d27eb7e69b71
parent 248339 6399ee520bcca0eb10b633de307ea11c0efb8b58
child 248341 a4c67bb878ff39c7d9ed3cdd57250a84c3db81f6
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, tn
bugs1076241
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1076241 - Add an API for setting a resolution on a document and scaling it by that amount. r=kats,tn
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
gfx/layers/Layers.cpp
gfx/layers/Layers.h
gfx/layers/composite/ContainerLayerComposite.h
gfx/layers/ipc/LayerTransactionParent.cpp
gfx/layers/ipc/LayersMessages.ipdlh
layout/base/nsDisplayList.cpp
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
layout/base/nsPresState.h
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -506,16 +506,37 @@ nsDOMWindowUtils::SetResolution(float aX
     sf->SetResolution(gfxSize(aXResolution, aYResolution));
     presShell->SetResolution(aXResolution, aYResolution);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetResolutionAndScaleTo(float aXResolution, float aYResolution)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+  if (sf) {
+    sf->SetResolutionAndScaleTo(gfxSize(aXResolution, aYResolution));
+    presShell->SetResolutionAndScaleTo(aXResolution, aYResolution);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::GetResolution(float* aXResolution, float* aYResolution)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsIPresShell* presShell = GetPresShell();
   if (!presShell) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -46,17 +46,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 
-[scriptable, uuid(9621eb05-b498-4e87-a012-95d817987624)]
+[scriptable, uuid(4922a706-a17e-48e0-ab6f-9fe1bbe4e5f7)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -194,42 +194,47 @@ interface nsIDOMWindowUtils : nsISupport
   void setDisplayPortBaseForElement(in int32_t aX,
                                     in int32_t aY,
                                     in int32_t aWidth,
                                     in int32_t aHeight,
                                     in nsIDOMElement aElement);
 
   /**
    * Get/set the resolution at which rescalable web content is drawn.
-   * Currently this is only (some) thebes content.
    *
    * Setting a new resolution does *not* trigger reflow.  This API is
    * entirely separate from textZoom and fullZoom; a resolution scale
    * can be applied together with both textZoom and fullZoom.
    *
-   * The effect of is API for gfx code to allocate more or fewer
+   * The effect of this API is for gfx code to allocate more or fewer
    * pixels for rescalable content by a factor of |resolution| in
-   * either or both dimensions.  setResolution() together with
-   * setDisplayport() can be used to implement a non-reflowing
-   * scale-zoom in concert with another entity that can draw with a
-   * scale.  For example, to scale a content |window| inside a
-   * <browser> by a factor of 2.0
-   *
-   *   window.setDisplayport(x, y, oldW / 2.0, oldH / 2.0);
-   *   window.setResolution(2.0, 2.0);
-   *   // elsewhere
-   *   browser.setViewportScale(2.0, 2.0);
+   * either or both dimensions.  The scale at which the content is
+   * displayed does not change; if that is desired, use
+   * setResolutionAndScaleTo() instead.
    *
    * The caller of this method must have chrome privileges.
    */
   void setResolution(in float aXResolution, in float aYResolution);
 
   void getResolution(out float aXResolution, out float aYResolution);
 
   /**
+   * Similar to setResolution(), but also scales the content by the
+   * amount of the resolution, so that it is displayed at a
+   * correspondingly larger or smaller size, without the need for
+   * the caller to set an additional transform.
+   *
+   * This can be used to implement a non-reflowing scale-zoom, e.g.
+   * for pinch-zoom on mobile platforms.
+   *
+   * The caller of this method must have chrome privileges.
+   */
+  void setResolutionAndScaleTo(in float aXResolution, in float aYResolution);
+
+  /**
    * Whether the resolution has been set by the user.
    * This gives a way to check whether the provided resolution is the default
    * value or restored from a previous session.
    *
    * Can only be accessed with chrome privileges.
    */
   readonly attribute boolean isResolutionSet;
 
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -732,33 +732,33 @@ Layer::IsScrollInfoLayer() const
       && HasScrollableFrameMetrics()
       && !GetFirstChild();
 }
 
 const Matrix4x4
 Layer::GetTransform() const
 {
   Matrix4x4 transform = mTransform;
-  transform.PostScale(mPostXScale, mPostYScale, 1.0f);
+  transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f);
   if (const ContainerLayer* c = AsContainerLayer()) {
     transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f);
   }
   return transform;
 }
 
 const Matrix4x4
 Layer::GetLocalTransform()
 {
   Matrix4x4 transform;
   if (LayerComposite* shadow = AsLayerComposite())
     transform = shadow->GetShadowTransform();
   else
     transform = mTransform;
 
-  transform.PostScale(mPostXScale, mPostYScale, 1.0f);
+  transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f);
   if (ContainerLayer* c = AsContainerLayer()) {
     transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f);
   }
 
   return transform;
 }
 
 void
@@ -853,16 +853,18 @@ Layer::TransformRectToRenderTarget(const
 ContainerLayer::ContainerLayer(LayerManager* aManager, void* aImplData)
   : Layer(aManager, aImplData),
     mFirstChild(nullptr),
     mLastChild(nullptr),
     mPreXScale(1.0f),
     mPreYScale(1.0f),
     mInheritedXScale(1.0f),
     mInheritedYScale(1.0f),
+    mPresShellResolution(1.0f),
+    mScaleToResolution(false),
     mUseIntermediateSurface(false),
     mSupportsComponentAlphaChildren(false),
     mMayHaveReadbackChild(false),
     mChildrenChanged(false)
 {
   mContentFlags = 0; // Clear NO_TEXT, NO_TEXT_OVER_TRANSPARENT
 }
 
@@ -1012,16 +1014,17 @@ ContainerLayer::RepositionChild(Layer* a
   return true;
 }
 
 void
 ContainerLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
 {
   aAttrs = ContainerLayerAttributes(mPreXScale, mPreYScale,
                                     mInheritedXScale, mInheritedYScale,
+                                    mPresShellResolution, mScaleToResolution,
                                     reinterpret_cast<uint64_t>(mHMDInfo.get()));
 }
 
 bool
 ContainerLayer::HasMultipleChildren()
 {
   uint32_t count = 0;
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
@@ -1676,16 +1679,19 @@ ContainerLayer::PrintInfo(std::stringstr
 {
   Layer::PrintInfo(aStream, aPrefix);
   if (UseIntermediateSurface()) {
     aStream << " [usesTmpSurf]";
   }
   if (1.0 != mPreXScale || 1.0 != mPreYScale) {
     aStream << nsPrintfCString(" [preScale=%g, %g]", mPreXScale, mPreYScale).get();
   }
+  if (mScaleToResolution) {
+    aStream << nsPrintfCString(" [presShellResolution=%g]", mPresShellResolution).get();
+  }
   if (mHMDInfo) {
     aStream << nsPrintfCString(" [hmd=%p]", mHMDInfo.get()).get();
   }
 }
 
 void
 ContainerLayer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent)
 {
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1221,18 +1221,19 @@ public:
   Layer* GetNextSibling() { return mNextSibling; }
   const Layer* GetNextSibling() const { return mNextSibling; }
   Layer* GetPrevSibling() { return mPrevSibling; }
   const Layer* GetPrevSibling() const { return mPrevSibling; }
   virtual Layer* GetFirstChild() const { return nullptr; }
   virtual Layer* GetLastChild() const { return nullptr; }
   const gfx::Matrix4x4 GetTransform() const;
   const gfx::Matrix4x4& GetBaseTransform() const { return mTransform; }
-  float GetPostXScale() const { return mPostXScale; }
-  float GetPostYScale() const { return mPostYScale; }
+  // Note: these are virtual because ContainerLayerComposite overrides them.
+  virtual float GetPostXScale() const { return mPostXScale; }
+  virtual float GetPostYScale() const { return mPostYScale; }
   bool GetIsFixedPosition() { return mIsFixedPosition; }
   bool GetIsStickyPosition() { return mStickyPositionData; }
   LayerPoint GetFixedPositionAnchor() { return mAnchor; }
   const LayerMargin& GetFixedPositionMargins() { return mMargins; }
   FrameMetrics::ViewID GetStickyScrollContainerId() { return mStickyPositionData->mScrollId; }
   const LayerRect& GetStickyScrollRangeOuter() { return mStickyPositionData->mOuter; }
   const LayerRect& GetStickyScrollRangeInner() { return mStickyPositionData->mInner; }
   FrameMetrics::ViewID GetScrollbarTargetContainerId() { return mScrollbarTargetId; }
@@ -1850,31 +1851,45 @@ public:
     }
 
     MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) InheritedScale", this));
     mInheritedXScale = aXScale;
     mInheritedYScale = aYScale;
     Mutated();
   }
 
+  void SetScaleToResolution(bool aScaleToResolution, float aResolution)
+  {
+    if (mScaleToResolution == aScaleToResolution && mPresShellResolution == aResolution) {
+      return;
+    }
+
+    MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ScaleToResolution", this));
+    mScaleToResolution = aScaleToResolution;
+    mPresShellResolution = aResolution;
+    Mutated();
+  }
+
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) MOZ_OVERRIDE;
 
   void SortChildrenBy3DZOrder(nsTArray<Layer*>& aArray);
 
   // These getters can be used anytime.
 
   virtual ContainerLayer* AsContainerLayer() MOZ_OVERRIDE { return this; }
   virtual const ContainerLayer* AsContainerLayer() const MOZ_OVERRIDE { return this; }
 
   virtual Layer* GetFirstChild() const MOZ_OVERRIDE { return mFirstChild; }
   virtual Layer* GetLastChild() const MOZ_OVERRIDE { return mLastChild; }
   float GetPreXScale() const { return mPreXScale; }
   float GetPreYScale() const { return mPreYScale; }
   float GetInheritedXScale() const { return mInheritedXScale; }
   float GetInheritedYScale() const { return mInheritedYScale; }
+  float GetPresShellResolution() const { return mPresShellResolution; }
+  bool ScaleToResolution() const { return mScaleToResolution; }
 
   MOZ_LAYER_DECL_NAME("ContainerLayer", TYPE_CONTAINER)
 
   /**
    * ContainerLayer backends need to override ComputeEffectiveTransforms
    * since the decision about whether to use a temporary surface for the
    * container is backend-specific. ComputeEffectiveTransforms must also set
    * mUseIntermediateSurface.
@@ -1963,16 +1978,21 @@ protected:
   Layer* mFirstChild;
   Layer* mLastChild;
   float mPreXScale;
   float mPreYScale;
   // The resolution scale inherited from the parent layer. This will already
   // be part of mTransform.
   float mInheritedXScale;
   float mInheritedYScale;
+  // For layers corresponding to an nsDisplayResolution, the resolution of the
+  // associated pres shell; for other layers, 1.0.
+  float mPresShellResolution;
+  // Whether the compositor should scale to mPresShellResolution.
+  bool mScaleToResolution;
   bool mUseIntermediateSurface;
   bool mSupportsComponentAlphaChildren;
   bool mMayHaveReadbackChild;
   // This is updated by ComputeDifferences. This will be true if we need to invalidate
   // the intermediate surface.
   bool mChildrenChanged;
   nsRefPtr<gfx::VRHMDInfo> mHMDInfo;
 };
--- a/gfx/layers/composite/ContainerLayerComposite.h
+++ b/gfx/layers/composite/ContainerLayerComposite.h
@@ -87,16 +87,34 @@ public:
 
   virtual void CleanupResources() MOZ_OVERRIDE;
 
   virtual LayerComposite* AsLayerComposite() MOZ_OVERRIDE { return this; }
 
   // container layers don't use a compositable
   CompositableHost* GetCompositableHost() MOZ_OVERRIDE { return nullptr; }
 
+  // If the layer is marked as scale-to-resolution, add a post-scale
+  // to the layer's transform equal to the pres shell resolution we're
+  // scaling to. This cancels out the post scale of '1 / resolution'
+  // added by Layout. TODO: It would be nice to get rid of both of these
+  // post-scales.
+  virtual float GetPostXScale() const MOZ_OVERRIDE {
+    if (mScaleToResolution) {
+      return mPostXScale * mPresShellResolution;
+    }
+    return mPostXScale;
+  }
+  virtual float GetPostYScale() const MOZ_OVERRIDE {
+    if (mScaleToResolution) {
+      return mPostYScale * mPresShellResolution;
+    }
+    return mPostYScale;
+  }
+
   virtual const char* Name() const MOZ_OVERRIDE { return "ContainerLayerComposite"; }
   UniquePtr<PreparedData> mPrepared;
 
   RefPtr<CompositingRenderTarget> mLastIntermediateSurface;
 };
 
 class RefLayerComposite : public RefLayer,
                           public LayerComposite
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -377,16 +377,18 @@ LayerTransactionParent::RecvUpdate(const
         ContainerLayerComposite* containerLayer = layerParent->AsContainerLayerComposite();
         if (!containerLayer) {
           return false;
         }
         const ContainerLayerAttributes& attrs =
           specific.get_ContainerLayerAttributes();
         containerLayer->SetPreScale(attrs.preXScale(), attrs.preYScale());
         containerLayer->SetInheritedScale(attrs.inheritedXScale(), attrs.inheritedYScale());
+        containerLayer->SetScaleToResolution(attrs.scaleToResolution(),
+                                             attrs.presShellResolution());
 
         if (attrs.hmdInfo()) {
           if (!IsSameProcess()) {
             NS_WARNING("VR layers currently not supported with cross-process compositing");
             return false;
           }
           containerLayer->SetVRHMDInfo(reinterpret_cast<mozilla::gfx::VRHMDInfo*>(attrs.hmdInfo()));
         }
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -227,16 +227,18 @@ struct CommonLayerAttributes {
 struct PaintedLayerAttributes {
   nsIntRegion validRegion;
 };
 struct ContainerLayerAttributes {
   float preXScale;
   float preYScale;
   float inheritedXScale;
   float inheritedYScale;
+  float presShellResolution;
+  bool scaleToResolution;
   // This is a bare pointer; LayerTransactionParent::RecvUpdate prevents this
   // from being used when !IsSameProcess(), but we should make this truly
   // cross process at some point by passing the HMDConfig
   uint64_t hmdInfo;
 };
 struct ColorLayerAttributes     { LayerColor color; nsIntRect bounds; };
 struct CanvasLayerAttributes    { GraphicsFilterType filter; nsIntRect bounds; };
 struct RefLayerAttributes       { int64_t id; };
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1574,16 +1574,18 @@ already_AddRefed<LayerManager> nsDisplay
 
   if (!root) {
     layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
     return nullptr;
   }
   // Root is being scaled up by the X/Y resolution. Scale it back down.
   root->SetPostScale(1.0f/containerParameters.mXScale,
                      1.0f/containerParameters.mYScale);
+  root->SetScaleToResolution(presShell->ScaleToResolution(),
+      containerParameters.mXScale);
 
   if (gfxPrefs::LayoutUseContainersForRootFrames()) {
     bool isRoot = presContext->IsRootContentDocument();
 
     nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
 
     nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize());
 
@@ -4148,16 +4150,18 @@ nsDisplayResolution::BuildLayer(nsDispla
   ContainerLayerParameters containerParameters(
     presShell->GetXResolution(), presShell->GetYResolution(), nsIntPoint(),
     aContainerParameters);
 
   nsRefPtr<Layer> layer = nsDisplaySubDocument::BuildLayer(
     aBuilder, aManager, containerParameters);
   layer->SetPostScale(1.0f / presShell->GetXResolution(),
                       1.0f / presShell->GetYResolution());
+  layer->AsContainerLayer()->SetScaleToResolution(
+      presShell->ScaleToResolution(), presShell->GetXResolution());
   return layer.forget();
 }
 
 nsDisplayStickyPosition::nsDisplayStickyPosition(nsDisplayListBuilder* aBuilder,
                                                  nsIFrame* aFrame,
                                                  nsDisplayList* aList)
   : nsDisplayOwnLayer(aBuilder, aFrame, aList)
 {
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -134,20 +134,20 @@ typedef struct CapturingContentInfo {
   // capture should only be allowed during a mousedown event
   bool mAllowed;
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   nsIContent* mContent;
 } CapturingContentInfo;
 
-// 79c0f49f-77f1-4cc5-80d1-6552e85ccb0c
+// 9d010f90-2d90-471c-b640-038cc350c187
 #define NS_IPRESSHELL_IID \
-  { 0xa0a4b515, 0x0b91, 0x4f13, \
-    { 0xa0, 0x60, 0x4b, 0xfb, 0x35, 0x00, 0xdc, 0x00 } }
+  { 0x9d010f90, 0x2d90, 0x471c, \
+    { 0xb6, 0x40, 0x03, 0x8c, 0xc3, 0x50, 0xc1, 0x87 } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -1398,16 +1398,29 @@ public:
    */
   virtual nsresult SetResolution(float aXResolution, float aYResolution) = 0;
   gfxSize GetResolution() { return gfxSize(mXResolution, mYResolution); }
   float GetXResolution() { return mXResolution; }
   float GetYResolution() { return mYResolution; }
   virtual gfxSize GetCumulativeResolution() = 0;
 
   /**
+   * Similar to SetResolution() but also increases the scale of the content
+   * by the same amount.
+   */
+  virtual nsresult SetResolutionAndScaleTo(float aXResolution, float aYResolution) = 0;
+
+  /**
+   * Return whether we are scaling to the set resolution.
+   * This is initially false; it's set to true by a call to
+   * SetResolutionAndScaleTo(), and set to false by a call to SetResolution().
+   */
+  virtual bool ScaleToResolution() const = 0;
+
+  /**
    * Returns whether we are in a DrawWindow() call that used the
    * DRAWWINDOW_DO_NOT_FLUSH flag.
    */
   bool InDrawWindowNotFlushing() const
   { return mRenderFlags & STATE_DRAWWINDOW_NOT_FLUSHING; }
 
   /**
    * Set the isFirstPaint flag.
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5540,31 +5540,38 @@ void PresShell::SetIgnoreViewportScrolli
     return;
   }
   RenderingState state(this);
   state.mRenderFlags = ChangeFlag(state.mRenderFlags, aIgnore,
                                   STATE_IGNORING_VIEWPORT_SCROLLING);
   SetRenderingState(state);
 }
 
-nsresult PresShell::SetResolution(float aXResolution, float aYResolution)
+nsresult PresShell::SetResolutionImpl(float aXResolution, float aYResolution, bool aScaleToResolution)
 {
   if (!(aXResolution > 0.0 && aYResolution > 0.0)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   if (aXResolution == mXResolution && aYResolution == mYResolution) {
     return NS_OK;
   }
   RenderingState state(this);
   state.mXResolution = aXResolution;
   state.mYResolution = aYResolution;
   SetRenderingState(state);
+  mScaleToResolution = aScaleToResolution;
+
   return NS_OK;
 }
 
+bool PresShell::ScaleToResolution() const
+{
+  return mScaleToResolution;
+}
+
 gfxSize PresShell::GetCumulativeResolution()
 {
   gfxSize resolution = GetResolution();
   nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
   if (parentCtx) {
     resolution = resolution * parentCtx->PresShell()->GetCumulativeResolution();
   }
   return resolution;
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -192,17 +192,23 @@ public:
                   nsIntRect* aScreenRect) MOZ_OVERRIDE;
 
   virtual already_AddRefed<nsPIDOMWindow> GetRootWindow() MOZ_OVERRIDE;
 
   virtual LayerManager* GetLayerManager() MOZ_OVERRIDE;
 
   virtual void SetIgnoreViewportScrolling(bool aIgnore) MOZ_OVERRIDE;
 
-  virtual nsresult SetResolution(float aXResolution, float aYResolution) MOZ_OVERRIDE;
+  virtual nsresult SetResolution(float aXResolution, float aYResolution) MOZ_OVERRIDE {
+    return SetResolutionImpl(aXResolution, aYResolution, /* aScaleToResolution = */ false);
+  }
+  virtual nsresult SetResolutionAndScaleTo(float aXResolution, float aYResolution) MOZ_OVERRIDE {
+    return SetResolutionImpl(aXResolution, aYResolution, /* aScaleToResolution = */ true);
+  }
+  virtual bool ScaleToResolution() const MOZ_OVERRIDE;
   virtual gfxSize GetCumulativeResolution() MOZ_OVERRIDE;
 
   //nsIViewObserver interface
 
   virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      uint32_t aFlags) MOZ_OVERRIDE;
   virtual nsresult HandleEvent(nsIFrame* aFrame,
                                mozilla::WidgetGUIEvent* aEvent,
@@ -748,16 +754,18 @@ protected:
          const mozilla::WidgetKeyboardEvent& aEvent,
          bool aEmbeddedCancelled,
          size_t aChainIndex = 0);
   bool CanDispatchEvent(const mozilla::WidgetGUIEvent* aEvent = nullptr) const;
 
   // A list of images that are visible or almost visible.
   nsTHashtable< nsRefPtrHashKey<nsIImageLoadingContent> > mVisibleImages;
 
+  nsresult SetResolutionImpl(float aXResolution, float aYResolution, bool aScaleToResolution);
+
 #ifdef DEBUG
   // The reflow root under which we're currently reflowing.  Null when
   // not in reflow.
   nsIFrame*                 mCurrentReflowRoot;
   uint32_t                  mUpdateCount;
 #endif
 
 #ifdef MOZ_REFLOW_PERF
@@ -850,12 +858,17 @@ protected:
   bool                      mInResize : 1;
 
   bool                      mImageVisibilityVisited : 1;
 
   bool                      mNextPaintCompressed : 1;
 
   bool                      mHasCSSBackgroundColor : 1;
 
+  // Whether content should be scaled by the resolution amount. If this is
+  // not set, a transform that scales by the inverse of the resolution is
+  // applied to rendered layers.
+  bool                      mScaleToResolution : 1;
+
   static bool               sDisableNonTestMouseEvents;
 };
 
 #endif /* !defined(nsPresShell_h_) */
--- a/layout/base/nsPresState.h
+++ b/layout/base/nsPresState.h
@@ -17,16 +17,17 @@
 
 class nsPresState
 {
 public:
   nsPresState()
     : mContentData(nullptr)
     , mScrollState(0, 0)
     , mResolution(1.0, 1.0)
+    , mScaleToResolution(false)
     , mDisabledSet(false)
     , mDisabled(false)
   {}
 
   void SetScrollState(const nsPoint& aState)
   {
     mScrollState = aState;
   }
@@ -41,16 +42,26 @@ public:
     mResolution = aSize;
   }
 
   gfxSize GetResolution() const
   {
     return mResolution;
   }
 
+  void SetScaleToResolution(bool aScaleToResolution)
+  {
+    mScaleToResolution = aScaleToResolution;
+  }
+
+  bool GetScaleToResolution() const
+  {
+    return mScaleToResolution;
+  }
+
   void ClearNonScrollState()
   {
     mContentData = nullptr;
     mDisabledSet = false;
   }
 
   bool GetDisabled() const
   {
@@ -78,13 +89,14 @@ public:
     mContentData = aProperty;
   }
 
 // MEMBER VARIABLES
 protected:
   nsCOMPtr<nsISupports> mContentData;
   nsPoint mScrollState;
   gfxSize mResolution;
+  bool mScaleToResolution;
   bool mDisabledSet;
   bool mDisabled;
 };
 
 #endif /* nsPresState_h_ */
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1890,16 +1890,17 @@ ScrollFrameHelper::ScrollFrameHelper(nsC
   , mPostedReflowCallback(false)
   , mMayHaveDirtyFixedChildren(false)
   , mUpdateScrollbarAttributes(false)
   , mHasBeenScrolledRecently(false)
   , mCollapsedResizer(false)
   , mShouldBuildScrollableLayer(false)
   , mHasBeenScrolled(false)
   , mIsResolutionSet(false)
+  , mScaleToResolution(false)
 {
   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
     mScrollbarActivity = new ScrollbarActivity(do_QueryFrame(aOuter));
   }
 
   EnsureImageVisPrefsCached();
 
   if (IsAlwaysActive() &&
@@ -3259,16 +3260,26 @@ ScrollFrameHelper::GetResolution() const
   return mResolution;
 }
 
 void
 ScrollFrameHelper::SetResolution(const gfxSize& aResolution)
 {
   mResolution = aResolution;
   mIsResolutionSet = true;
+  mScaleToResolution = false;
+}
+
+void
+ScrollFrameHelper::SetResolutionAndScaleTo(const gfxSize& aResolution)
+{
+  MOZ_ASSERT(mIsRoot);  // This API should only be called on root scroll frames.
+  mResolution = aResolution;
+  mIsResolutionSet = true;
+  mScaleToResolution = true;
 }
 
 static void
 AdjustForWholeDelta(int32_t aDelta, nscoord* aCoord)
 {
   if (aDelta < 0) {
     *aCoord = nscoord_MIN;
   } else if (aDelta > 0) {
@@ -5056,30 +5067,40 @@ ScrollFrameHelper::SaveState() const
   // while we're in the process of loading content to scroll to a restored
   // position, we'll keep trying after the reframe.
   nsPoint pt = GetLogicalScrollPosition();
   if (mRestorePos.y != -1 && pt == mLastPos) {
     pt = mRestorePos;
   }
   state->SetScrollState(pt);
   state->SetResolution(mResolution);
+  state->SetScaleToResolution(mScaleToResolution);
   return state;
 }
 
 void
 ScrollFrameHelper::RestoreState(nsPresState* aState)
 {
   mRestorePos = aState->GetScrollState();
   mDidHistoryRestore = true;
   mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
   mResolution = aState->GetResolution();
   mIsResolutionSet = true;
+  mScaleToResolution = aState->GetScaleToResolution();
+
+  // Scaling-to-resolution should only be used on root scroll frames.
+  MOZ_ASSERT(mIsRoot || !mScaleToResolution);
 
   if (mIsRoot) {
-    mOuter->PresContext()->PresShell()->SetResolution(mResolution.width, mResolution.height);
+    nsIPresShell* presShell = mOuter->PresContext()->PresShell();
+    if (mScaleToResolution) {
+      presShell->SetResolutionAndScaleTo(mResolution.width, mResolution.height);
+    } else {
+      presShell->SetResolution(mResolution.width, mResolution.height);
+    }
   }
 }
 
 void
 ScrollFrameHelper::PostScrolledAreaEvent()
 {
   if (mScrolledAreaEvent.IsPending()) {
     return;
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -172,16 +172,17 @@ public:
     return pt;
   }
   nsRect GetScrollRange() const;
   // Get the scroll range assuming the scrollport has size (aWidth, aHeight).
   nsRect GetScrollRange(nscoord aWidth, nscoord aHeight) const;
   nsSize GetScrollPositionClampingScrollPortSize() const;
   gfxSize GetResolution() const;
   void SetResolution(const gfxSize& aResolution);
+  void SetResolutionAndScaleTo(const gfxSize& aResolution);
 
 protected:
   nsRect GetScrollRangeForClamping() const;
 
 public:
   static void AsyncScrollCallback(ScrollFrameHelper* aInstance,
                                   mozilla::TimeStamp aTime);
   static void AsyncSmoothMSDScrollCallback(ScrollFrameHelper* aInstance,
@@ -452,20 +453,24 @@ public:
   bool mShouldBuildScrollableLayer:1;
 
   // If true, add clipping in ScrollFrameHelper::ComputeFrameMetrics.
   bool mAddClipRectToLayer:1;
 
   // True if this frame has been scrolled at least once
   bool mHasBeenScrolled:1;
 
-  // True if the frame's resolution has been set via SetResolution or restored
-  // via RestoreState.
+  // True if the frame's resolution has been set via SetResolution or
+  // SetResolutionAndScaleTo or restored via RestoreState.
   bool mIsResolutionSet:1;
 
+  // True if the frame's resolution has been set via SetResolutionAndScaleTo.
+  // Only meaningful for root scroll frames.
+  bool mScaleToResolution:1;
+
 protected:
   /**
    * @note This method might destroy the frame, pres shell and other objects.
    */
   void ScrollToWithOrigin(nsPoint aScrollPosition,
                           nsIScrollableFrame::ScrollMode aMode,
                           nsIAtom *aOrigin, // nullptr indicates "other" origin
                           const nsRect* aRange);
@@ -638,16 +643,19 @@ public:
     return mHelper.GetScrollPositionClampingScrollPortSize();
   }
   virtual gfxSize GetResolution() const MOZ_OVERRIDE {
     return mHelper.GetResolution();
   }
   virtual void SetResolution(const gfxSize& aResolution) MOZ_OVERRIDE {
     return mHelper.SetResolution(aResolution);
   }
+  virtual void SetResolutionAndScaleTo(const gfxSize& aResolution) MOZ_OVERRIDE {
+    return mHelper.SetResolutionAndScaleTo(aResolution);
+  }
   virtual nsSize GetLineScrollAmount() const MOZ_OVERRIDE {
     return mHelper.GetLineScrollAmount();
   }
   virtual nsSize GetPageScrollAmount() const MOZ_OVERRIDE {
     return mHelper.GetPageScrollAmount();
   }
   /**
    * @note This method might destroy the frame, pres shell and other objects.
@@ -999,16 +1007,19 @@ public:
     return mHelper.GetScrollPositionClampingScrollPortSize();
   }
   virtual gfxSize GetResolution() const MOZ_OVERRIDE {
     return mHelper.GetResolution();
   }
   virtual void SetResolution(const gfxSize& aResolution) MOZ_OVERRIDE {
     return mHelper.SetResolution(aResolution);
   }
+  virtual void SetResolutionAndScaleTo(const gfxSize& aResolution) MOZ_OVERRIDE {
+    return mHelper.SetResolutionAndScaleTo(aResolution);
+  }
   virtual nsSize GetLineScrollAmount() const MOZ_OVERRIDE {
     return mHelper.GetLineScrollAmount();
   }
   virtual nsSize GetPageScrollAmount() const MOZ_OVERRIDE {
     return mHelper.GetPageScrollAmount();
   }
   /**
    * @note This method might destroy the frame, pres shell and other objects.
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -149,16 +149,22 @@ public:
    * Get the element resolution.
    */
   virtual gfxSize GetResolution() const = 0;
   /**
    * Set the element resolution.
    */
   virtual void SetResolution(const gfxSize& aResolution) = 0;
   /**
+   * Set the element resolution and specify that content should be scaled by
+   * the amount of the resolution. This is only meaningful for root scroll
+   * frames. See nsIDOMWindowUtils.setResolutionAndScaleTo().
+   */
+  virtual void SetResolutionAndScaleTo(const gfxSize& aResolution) = 0;
+  /**
    * Return how much we would try to scroll by in each direction if
    * asked to scroll by one "line" vertically and horizontally.
    */
   virtual nsSize GetLineScrollAmount() const = 0;
   /**
    * Return how much we would try to scroll by in each direction if
    * asked to scroll by one "page" vertically and horizontally.
    */