Bug 590294, part 5: Add a setDisplayPort() API to allow the frontend (or whatever else) to set an arbitrary visible region for the purposes of invalidation and layer retaining. r=roc sr=dbaron
authorChris Jones <jones.chris.g@gmail.com>
Fri, 03 Sep 2010 15:10:46 -0500
changeset 54080 a5f171570672749482392ef98f4c7f03071217cd
parent 54079 99279c1c33cc443a880f190a464fb59c75f186cc
child 54081 0b90c6b07933ff2cb7e56be41eb5e82516513592
push idunknown
push userunknown
push dateunknown
reviewersroc, dbaron
bugs590294
milestone2.0b6pre
Bug 590294, part 5: Add a setDisplayPort() API to allow the frontend (or whatever else) to set an arbitrary visible region for the purposes of invalidation and layer retaining. r=roc sr=dbaron
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/nsDisplayList.cpp
layout/base/nsIPresShell.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsPresShell.cpp
layout/ipc/RenderFrameParent.cpp
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -250,16 +250,40 @@ nsDOMWindowUtils::SetCSSViewport(float a
   nscoord height = nsPresContext::CSSPixelsToAppUnits(aHeightPx);
 
   presShell->ResizeReflow(width, height);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetDisplayPort(float aXPx, float aYPx,
+                                 float aWidthPx, float aHeightPx)
+{
+  if (!IsUniversalXPConnectCapable()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsIPresShell* presShell = GetPresShell();
+  if (!presShell) {
+    return NS_ERROR_FAILURE;
+  } 
+
+  nsRect displayport(nsPresContext::CSSPixelsToAppUnits(aXPx),
+                     nsPresContext::CSSPixelsToAppUnits(aYPx),
+                     nsPresContext::CSSPixelsToAppUnits(aWidthPx),
+                     nsPresContext::CSSPixelsToAppUnits(aHeightPx));
+  presShell->SetDisplayPort(displayport);
+
+  presShell->SetIgnoreViewportScrolling(PR_TRUE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
                                  float aX,
                                  float aY,
                                  PRInt32 aButton,
                                  PRInt32 aClickCount,
                                  PRInt32 aModifiers,
                                  PRBool aIgnoreRootScrollFrame)
 {
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -48,17 +48,17 @@
 interface nsIDOMNode;
 interface nsIDOMNodeList;
 interface nsIDOMElement;
 interface nsIDOMHTMLCanvasElement;
 interface nsIDOMEvent;
 interface nsITransferable;
 interface nsIQueryContentEventResult;
 
-[scriptable, uuid(46df9178-2b24-45a3-9c37-f00e999e9887)]
+[scriptable, uuid(b6628355-e72f-4d1d-9b83-ffc2740536b5)]
 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.
@@ -105,16 +105,45 @@ interface nsIDOMWindowUtils : nsISupport
    * pixels, regardless of the size of the enclosing widget/view.
    * This will trigger reflow.
    *
    * The caller of this method must have UniversalXPConnect
    * privileges.
    */
   void setCSSViewport(in float aWidthPx, in float aHeightPx);
 
+  /**
+   * Set the "displayport" to be <xPx, yPx, widthPx, heightPx> in
+   * units of CSS pixels, regardless of the size of the enclosing
+   * widget/view.  This will *not* trigger reflow.
+   *
+   * <x, y> is relative to the top-left of the CSS viewport.  This
+   * means that the pixels rendered to the displayport take scrolling
+   * into account, for example.
+   *
+   * The displayport will be used as the window's visible region for
+   * the purposes of invalidation and painting.  The displayport can
+   * approximately be thought of as a "persistent" drawWindow()
+   * (albeit with coordinates relative to the CSS viewport): the
+   * bounds are remembered by the platform, and layer pixels are
+   * retained and updated inside the viewport bounds.
+   *
+   * It's legal to set a displayport that extends beyond the CSS
+   * viewport in any direction (left/right/top/bottom).
+   * 
+   * It's also legal to set a displayport that extends beyond the
+   * document's bounds.  The value of the pixels rendered outside the
+   * document bounds is not yet defined.
+   *
+   * The caller of this method must have UniversalXPConnect
+   * privileges.
+   */
+  void setDisplayPort(in float aXPx, in float aYPx,
+                      in float aWidthPx, in float aHeightPx);
+
   /** Synthesize a mouse event. The event types supported are:
    *    mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu
    *
    * Events are sent in coordinates offset by aX and aY from the window.
    *
    * Note that additional events may be fired as a result of this call. For
    * instance, typically a click event will be fired as a result of a
    * mousedown and mouseup in sequence.
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -403,20 +403,42 @@ void nsDisplayList::PaintForFrame(nsDisp
     layerManager->BeginTransaction();
   }
 
   nsRefPtr<ContainerLayer> root = aBuilder->LayerBuilder()->
     BuildContainerLayerFor(aBuilder, layerManager, aForFrame, nsnull, *this);
   if (!root)
     return;
 
-  nsIntRect visible =
-    mVisibleRect.ToNearestPixels(aForFrame->PresContext()->AppUnitsPerDevPixel());
+  nsPresContext* presContext = aForFrame->PresContext();
+  nsIPresShell* presShell = presContext->GetPresShell();
+
+  nsIntRect visible = mVisibleRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
   root->SetVisibleRegion(nsIntRegion(visible));
 
+  // Collect frame metrics with which to stamp the root layer.
+  FrameMetrics metrics;
+
+  PRInt32 auPerCSSPixel = nsPresContext::AppUnitsPerCSSPixel();
+  metrics.mViewportSize =
+    presContext->GetVisibleArea().ToNearestPixels(auPerCSSPixel).Size();
+  if (presShell->UsingDisplayPort()) {
+    metrics.mDisplayPort =
+      presShell->GetDisplayPort().ToNearestPixels(auPerCSSPixel);
+  }
+
+  nsIScrollableFrame* rootScrollableFrame =
+    presShell->GetRootScrollFrameAsScrollable();
+  if (rootScrollableFrame) {
+    metrics.mViewportScrollOffset =
+      rootScrollableFrame->GetScrollPosition().ToNearestPixels(auPerCSSPixel);
+  }
+
+  root->SetFrameMetrics(metrics);
+
   layerManager->SetRoot(root);
   aBuilder->LayerBuilder()->WillEndTransaction(layerManager);
   layerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer,
                                aBuilder);
   aBuilder->LayerBuilder()->DidEndTransaction(layerManager);
 
   if (aFlags & PAINT_FLUSH_LAYERS) {
     FrameLayerBuilder::InvalidateAllLayers(layerManager);
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -58,16 +58,17 @@
 #include "nsHashKeys.h"
 #include "nsISupports.h"
 #include "nsQueryFrame.h"
 #include "nsCoord.h"
 #include "nsColor.h"
 #include "nsEvent.h"
 #include "nsCompatibility.h"
 #include "nsFrameManagerBase.h"
+#include "nsRect.h"
 #include "mozFlushType.h"
 #include "nsWeakReference.h"
 #include <stdio.h> // for FILE definition
 #include "nsChangeHint.h"
 
 class nsIContent;
 class nsIDocument;
 class nsIFrame;
@@ -94,17 +95,16 @@ class nsIScrollableFrame;
 class gfxASurface;
 class gfxContext;
 class nsIDOMEvent;
 class nsDisplayList;
 class nsDisplayListBuilder;
 class nsPIDOMWindow;
 struct nsPoint;
 struct nsIntPoint;
-struct nsRect;
 struct nsIntRect;
 class nsRefreshDriver;
 class nsARefreshObserver;
 
 typedef short SelectionType;
 typedef PRUint64 nsFrameState;
 
 namespace mozilla {
@@ -134,18 +134,18 @@ typedef struct CapturingContentInfo {
   nsIContent* mContent;
 
   CapturingContentInfo() :
     mAllowed(PR_FALSE), mRetargetToElement(PR_FALSE), mPreventDrag(PR_FALSE),
     mContent(nsnull) { }
 } CapturingContentInfo;
 
 #define NS_IPRESSHELL_IID     \
-  { 0xe63a350c, 0x4e04, 0x4056, \
-    { 0x8d, 0xa0, 0x51, 0xcc, 0x55, 0x68, 0x68, 0x42 } }
+ { 0x34f80395, 0xff82, 0x49fa, \
+    { 0x9c, 0x83, 0xa6, 0xba, 0x49, 0xa8, 0x55, 0x4a } }
 
 // Constants for ScrollContentIntoView() function
 #define NS_PRESSHELL_SCROLL_TOP      0
 #define NS_PRESSHELL_SCROLL_BOTTOM   100
 #define NS_PRESSHELL_SCROLL_LEFT     0
 #define NS_PRESSHELL_SCROLL_RIGHT    100
 #define NS_PRESSHELL_SCROLL_CENTER   50
 #define NS_PRESSHELL_SCROLL_ANYWHERE -1
@@ -191,16 +191,21 @@ public:
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIPresShell_base, NS_IPRESSHELL_IID)
 
 class nsIPresShell : public nsIPresShell_base
 {
 protected:
   typedef mozilla::layers::LayerManager LayerManager;
 
+  enum {
+    STATE_IGNORING_VIEWPORT_SCROLLING = 0x1,
+    STATE_USING_DISPLAYPORT = 0x2
+  };
+
 public:
   virtual NS_HIDDEN_(nsresult) Init(nsIDocument* aDocument,
                                    nsPresContext* aPresContext,
                                    nsIViewManager* aViewManager,
                                    nsStyleSet* aStyleSet,
                                    nsCompatibility aCompatMode) = 0;
 
   /**
@@ -1052,16 +1057,44 @@ public:
 
   /**
    * Get the layer manager for the widget of the root view, if it has
    * one.
    */
   virtual LayerManager* GetLayerManager() = 0;
 
   /**
+   * Track whether we're ignoring viewport scrolling for the purposes
+   * of painting.  If we are ignoring, then layers aren't clipped to
+   * the CSS viewport and scrollbars aren't drawn.
+   */
+  virtual void SetIgnoreViewportScrolling(PRBool aIgnore) = 0;
+  PRBool IgnoringViewportScrolling() const
+  { return mRenderFlags & STATE_IGNORING_VIEWPORT_SCROLLING; }
+
+  /**
+   * Set up a "displayport", which overrides what everything else thinks
+   * is the visible region of this document with the specified
+   * displayport rect.
+   */
+  virtual void SetDisplayPort(const nsRect& aDisplayPort) = 0;
+  PRBool UsingDisplayPort() const
+  { return mRenderFlags & STATE_USING_DISPLAYPORT; }
+
+  /**
+   * Return the displayport being used.  |UsingDisplayPort()| must be
+   * true.
+   */
+  nsRect GetDisplayPort()
+  {
+    NS_ABORT_IF_FALSE(UsingDisplayPort(), "no displayport defined!");
+    return mDisplayPort;
+  }
+
+  /**
    * Refresh observer management.
    */
 protected:
   virtual PRBool AddRefreshObserverExternal(nsARefreshObserver* aObserver,
                                             mozFlushType aFlushType);
   PRBool AddRefreshObserverInternal(nsARefreshObserver* aObserver,
                                     mozFlushType aFlushType);
   virtual PRBool RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
@@ -1152,16 +1185,27 @@ protected:
   PRPackedBool              mSuppressInterruptibleReflows;
 
   // A list of weak frames. This is a pointer to the last item in the list.
   nsWeakFrame*              mWeakFrames;
 
   // Most recent canvas background color.
   nscolor                   mCanvasBackgroundColor;
 
+  // Flags controlling how our document is rendered.  These persist
+  // between paints and so are tied with retained layer pixels.
+  // PresShell flushes retained layers when the rendering state
+  // changes in a way that prevents us from being able to (usefully)
+  // re-use old pixels.
+  PRUint32                  mRenderFlags;
+  // If displayport rendering has been requested, |UsingDisplayPort()|
+  // is true and |mDisplayPort| defines the "visible rect" we
+  // maintain.
+  nsRect                    mDisplayPort;
+
   // Live pres shells, for memory and other tracking
   typedef nsPtrHashKey<nsIPresShell> PresShellPtrKey;
   static nsTHashtable<PresShellPtrKey> *sLiveShells;
 
   static nsIContent* gKeyDownTarget;
 };
 
 /**
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1225,59 +1225,68 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
       aFlags &= ~PAINT_WIDGET_LAYERS;
       NS_ASSERTION(aRenderingContext, "need a rendering context");
     }
   }
 
   nsPresContext* presContext = aFrame->PresContext();
   nsIPresShell* presShell = presContext->PresShell();
 
+  PRBool ignoreViewportScrolling = presShell->IgnoringViewportScrolling();
   nsRegion visibleRegion;
-  if ((aFlags & PAINT_WIDGET_LAYERS) &&
-      !(aFlags & PAINT_IGNORE_VIEWPORT_SCROLLING)) {
+  if (aFlags & PAINT_WIDGET_LAYERS) {
     // This layer tree will be reused, so we'll need to calculate it
-    // for the whole visible area of the window
-    visibleRegion = aFrame->GetOverflowRectRelativeToSelf();
+    // for the whole "visible" area of the window
+    // 
+    // |ignoreViewportScrolling| and |usingDisplayPort| are persistent
+    // document-rendering state.  We rely on PresShell to flush
+    // retained layers as needed when that persistent state changes.
+    if (!presShell->UsingDisplayPort()) {
+      visibleRegion = aFrame->GetOverflowRectRelativeToSelf();
+    } else {
+      visibleRegion = presShell->GetDisplayPort();
+    }
   } else {
     visibleRegion = aDirtyRegion;
   }
 
   // If we're going to display something different from what we'd normally
   // paint in a window then we will flush out any retained layer trees before
   // *and after* we draw.
-  PRBool willFlushLayers = aFlags & (PAINT_IGNORE_VIEWPORT_SCROLLING |
-                                     PAINT_HIDE_CARET);
+  PRBool willFlushRetainedLayers = (aFlags & PAINT_HIDE_CARET) != 0;
 
   nsDisplayListBuilder builder(aFrame, PR_FALSE, !(aFlags & PAINT_HIDE_CARET));
   nsDisplayList list;
   if (aFlags & PAINT_IN_TRANSFORM) {
     builder.SetInTransform(PR_TRUE);
   }
   if (aFlags & PAINT_SYNC_DECODE_IMAGES) {
     builder.SetSyncDecodeImages(PR_TRUE);
   }
   if (aFlags & PAINT_WIDGET_LAYERS) {
     builder.SetPaintingToWindow(PR_TRUE);
   }
   if (aFlags & PAINT_IGNORE_SUPPRESSION) {
     builder.IgnorePaintSuppression();
   }
   nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
-  if (aFlags & PAINT_IGNORE_VIEWPORT_SCROLLING) {
+  if (ignoreViewportScrolling) {
     NS_ASSERTION(!aFrame->GetParent(), "must have root frame");
     nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
     if (rootScrollFrame) {
       nsIScrollableFrame* rootScrollableFrame =
         presShell->GetRootScrollFrameAsScrollable();
       if (aFlags & PAINT_DOCUMENT_RELATIVE) {
         // Make visibleRegion and aRenderingContext relative to the
         // scrolled frame instead of the root frame.
         nsPoint pos = rootScrollableFrame->GetScrollPosition();
         visibleRegion.MoveBy(-pos);
-        aRenderingContext->Translate(pos.x, pos.y);
+        if (aRenderingContext) {
+          aRenderingContext->Translate(pos.x, pos.y);
+        }
       }
       builder.SetIgnoreScrollFrame(rootScrollFrame);
 
       nsCanvasFrame* canvasFrame =
         do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
       if (canvasFrame) {
         // Use UnionRect here to ensure that areas where the scrollbars
         // were are still filled with the background color.
@@ -1347,36 +1356,36 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
     // Add the canvas background color to the bottom of the list. This
     // happens after we've built the list so that AddCanvasBackgroundColorItem
     // can monkey with the contents if necessary.
     rv = presShell->AddCanvasBackgroundColorItem(
            builder, list, aFrame, canvasArea, aBackstop);
 
     // If the passed in backstop color makes us draw something different from
     // normal, we need to flush layers.
-    if ((aFlags & PAINT_WIDGET_LAYERS) && !willFlushLayers) {
+    if ((aFlags & PAINT_WIDGET_LAYERS) && !willFlushRetainedLayers) {
       nsIView* view = aFrame->GetView();
       if (view) {
         nscolor backstop = presShell->ComputeBackstopColor(view);
         // The PresShell's canvas background color doesn't get updated until
         // EnterPresShell, so this check has to be done after that.
         nscolor canvasColor = presShell->GetCanvasBackground();
         if (NS_ComposeColors(aBackstop, canvasColor) !=
             NS_ComposeColors(backstop, canvasColor)) {
-          willFlushLayers = PR_TRUE;
+          willFlushRetainedLayers = PR_TRUE;
         }
       }
     }
   }
 
   builder.LeavePresShell(aFrame, dirtyRect);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (builder.GetHadToIgnorePaintSuppression()) {
-    willFlushLayers = PR_TRUE;
+    willFlushRetainedLayers = PR_TRUE;
   }
 
 #ifdef DEBUG
   if (gDumpPaintList) {
     fprintf(stderr, "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
             dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
     nsFrame::PrintDisplayList(&builder, list);
   }
@@ -1394,20 +1403,24 @@ nsLayoutUtils::PaintFrame(nsIRenderingCo
   PRUint32 flags = nsDisplayList::PAINT_DEFAULT;
   if (aFlags & PAINT_WIDGET_LAYERS) {
     flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
     nsIWidget *widget = aFrame->GetNearestWidget();
     PRInt32 pixelRatio = presContext->AppUnitsPerDevPixel();
     nsIntRegion visibleWindowRegion(visibleRegion.ToOutsidePixels(pixelRatio));
     nsIntRegion dirtyWindowRegion(aDirtyRegion.ToOutsidePixels(pixelRatio));
 
-    if (willFlushLayers) {
-      // We're going to display something different from what we'd normally
-      // paint in a window, so make sure we flush out any retained layer
-      // trees before *and after* we draw
+    if (willFlushRetainedLayers) {
+      // The caller wanted to paint from retained layers, but set up
+      // the paint in such a way that we can't use them.  We're going
+      // to display something different from what we'd normally paint
+      // in a window, so make sure we flush out any retained layer
+      // trees before *and after* we draw.  Callers should be fixed to
+      // not do this.
+      NS_WARNING("Flushing retained layers!");
       flags |= nsDisplayList::PAINT_FLUSH_LAYERS;
     } else if (widget && !(aFlags & PAINT_DOCUMENT_RELATIVE)) {
       // XXX we should simplify this API now that dirtyWindowRegion always
       // covers the entire window
       widget->UpdatePossiblyTransparentRegion(dirtyWindowRegion, visibleWindowRegion);
     }
   }
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -514,18 +514,17 @@ public:
 
   enum {
     PAINT_IN_TRANSFORM = 0x01,
     PAINT_SYNC_DECODE_IMAGES = 0x02,
     PAINT_WIDGET_LAYERS = 0x04,
     PAINT_IGNORE_SUPPRESSION = 0x08,
     PAINT_DOCUMENT_RELATIVE = 0x10,
     PAINT_HIDE_CARET = 0x20,
-    PAINT_ALL_CONTINUATIONS = 0x40,
-    PAINT_IGNORE_VIEWPORT_SCROLLING = 0x80
+    PAINT_ALL_CONTINUATIONS = 0x40
   };
 
   /**
    * Given aFrame, the root frame of a stacking context, paint it and its
    * descendants to aRenderingContext.
    * @param aRenderingContext a rendering context translated so that (0,0)
    * is the origin of aFrame; for best results, (0,0) should transform
    * to pixel-aligned coordinates. This can be null, in which case
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -234,16 +234,28 @@ static NS_DEFINE_IID(kRangeCID,     NS_R
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 PRBool nsIPresShell::gIsAccessibilityActive = PR_FALSE;
 CapturingContentInfo nsIPresShell::gCaptureInfo;
 nsIContent* nsIPresShell::gKeyDownTarget;
 
+static PRUint32
+ChangeFlag(PRUint32 aFlags, PRBool aOnOff, PRUint32 aFlag)
+{
+  PRUint32 flags;
+  if (aOnOff) {
+    flags = (aFlags | aFlag);
+  } else {
+    flags = (aFlag & ~aFlag);
+  }
+  return flags;
+}
+
 // convert a color value to a string, in the CSS format #RRGGBB
 // *  - initially created for bugs 31816, 20760, 22963
 static void ColorToString(nscolor aColor, nsAutoString &aString);
 
 // Class ID's
 static NS_DEFINE_CID(kFrameSelectionCID, NS_FRAMESELECTION_CID);
 
 // RangePaintInfo is used to paint ranges to offscreen buffers
@@ -810,16 +822,20 @@ public:
   virtual already_AddRefed<gfxASurface> RenderSelection(nsISelection* aSelection,
                                                         nsIntPoint& aPoint,
                                                         nsIntRect* aScreenRect);
 
   virtual already_AddRefed<nsPIDOMWindow> GetRootWindow();
 
   virtual LayerManager* GetLayerManager();
 
+  virtual void SetIgnoreViewportScrolling(PRBool aIgnore);
+
+  virtual void SetDisplayPort(const nsRect& aDisplayPort);
+
   //nsIViewObserver interface
 
   NS_IMETHOD Paint(nsIView* aDisplayRoot,
                    nsIView* aViewToPaint,
                    nsIWidget* aWidget,
                    const nsRegion& aDirtyRegion,
                    const nsIntRegion& aIntDirtyRegion,
                    PRBool aPaintDefaultBackground,
@@ -995,16 +1011,46 @@ protected:
   void VerifyHasDirtyRootAncestor(nsIFrame* aFrame);
 #endif
 
   // Helper for ScrollContentIntoView
   void DoScrollContentIntoView(nsIContent* aContent,
                                PRIntn      aVPercent,
                                PRIntn      aHPercent);
 
+  friend struct AutoRenderingStateSaveRestore;
+  friend struct RenderingState;
+
+  struct RenderingState {
+    RenderingState(PresShell* aPresShell) 
+      : mRenderFlags(aPresShell->mRenderFlags)
+      , mDisplayPort(aPresShell->mDisplayPort)
+    { }
+    PRUint32 mRenderFlags;
+    nsRect mDisplayPort;
+  };
+
+  struct AutoSaveRestoreRenderingState {
+    AutoSaveRestoreRenderingState(PresShell* aPresShell)
+      : mPresShell(aPresShell)
+      , mOldState(aPresShell)
+    {}
+
+    ~AutoSaveRestoreRenderingState()
+    {
+      mPresShell->mRenderFlags = mOldState.mRenderFlags;
+      mPresShell->mDisplayPort = mOldState.mDisplayPort;
+    }
+
+    PresShell* mPresShell;
+    RenderingState mOldState;
+  };
+
+  void SetRenderingState(const RenderingState& aState);
+
   friend class nsPresShellEventCB;
 
   PRBool mCaretEnabled;
 #ifdef NS_DEBUG
   nsresult CloneStyleSet(nsStyleSet* aSet, nsStyleSet** aResult);
   PRBool VerifyIncrementalReflow();
   PRBool mInVerifyReflow;
   void ShowEventTargetDebug();
@@ -1599,16 +1645,17 @@ PresShell::PresShell()
 #endif
   mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
   mIsThemeSupportDisabled = PR_FALSE;
   mIsActive = PR_TRUE;
   mFrozen = PR_FALSE;
 #ifdef DEBUG
   mPresArenaAllocCount = 0;
 #endif
+  mRenderFlags = 0;
 
   static bool registeredReporter = false;
   if (!registeredReporter) {
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(LayoutPresShell));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(LayoutBidi));
     registeredReporter = true;
   }
 
@@ -5267,41 +5314,57 @@ PresShell::RenderDocument(const nsRect& 
   gfxFloat scale = gfxFloat(devCtx->AppUnitsPerDevPixel())/nsPresContext::AppUnitsPerCSSPixel();
   aThebesContext->Scale(scale, scale);
 
   // Since canvas APIs use floats to set up their matrices, we may have
   // some slight inaccuracy here. Adjust matrix components that are
   // integers up to the accuracy of floats to be those integers.
   aThebesContext->NudgeCurrentMatrixToIntegers();
 
+  AutoSaveRestoreRenderingState _(this);
+
   nsCOMPtr<nsIRenderingContext> rc;
   devCtx->CreateRenderingContextInstance(*getter_AddRefs(rc));
   rc->Init(devCtx, aThebesContext);
 
+  PRBool wouldFlushRetainedLayers = PR_FALSE;
   PRUint32 flags = nsLayoutUtils::PAINT_IGNORE_SUPPRESSION;
   if (!(aFlags & RENDER_ASYNC_DECODE_IMAGES)) {
     flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES;
   }
   if (aFlags & RENDER_USE_WIDGET_LAYERS) {
     // We only support using widget layers on display root's with widgets.
     nsIView* view = rootFrame->GetView();
     if (view && view->GetWidget() &&
         nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
       flags |= nsLayoutUtils::PAINT_WIDGET_LAYERS;
     }
   }
   if (!(aFlags & RENDER_CARET)) {
+    wouldFlushRetainedLayers = PR_TRUE;
     flags |= nsLayoutUtils::PAINT_HIDE_CARET;
   }
   if (aFlags & RENDER_IGNORE_VIEWPORT_SCROLLING) {
-    flags |= nsLayoutUtils::PAINT_IGNORE_VIEWPORT_SCROLLING;
+    wouldFlushRetainedLayers = !IgnoringViewportScrolling();
+    ChangeFlag(mRenderFlags, PR_TRUE, STATE_IGNORING_VIEWPORT_SCROLLING);
   }
   if (aFlags & RENDER_DOCUMENT_RELATIVE) {
+    // XXX be smarter about this ... drawWindow might want a rect
+    // that's "pretty close" to what our retained layer tree covers.
+    // In that case, it wouldn't disturb normal rendering too much,
+    // and we should allow it.
+    wouldFlushRetainedLayers = PR_TRUE;
     flags |= nsLayoutUtils::PAINT_DOCUMENT_RELATIVE;
   }
+
+  // Don't let drawWindow blow away our retained layer tree
+  if ((flags & nsLayoutUtils::PAINT_WIDGET_LAYERS) && wouldFlushRetainedLayers) {
+    flags &= ~nsLayoutUtils::PAINT_WIDGET_LAYERS;
+  }
+
   nsLayoutUtils::PaintFrame(rc, rootFrame, nsRegion(aRect),
                             aBackgroundColor, flags);
 
   // if we had to use a group, paint it to the destination now
   if (needsGroup) {
     aThebesContext->Restore();
     aThebesContext->PopGroupToSource();
     aThebesContext->Paint();
@@ -5853,16 +5916,63 @@ LayerManager* PresShell::GetLayerManager
   if (NS_SUCCEEDED(mViewManager->GetRootView(rootView)) && rootView) {
     if (nsIWidget* widget = rootView->GetWidget()) {
       return widget->GetLayerManager();
     }
   }
   return nsnull;
 }
 
+void PresShell::SetIgnoreViewportScrolling(PRBool aIgnore)
+{
+  if (IgnoringViewportScrolling() == aIgnore) {
+    return;
+  }
+  RenderingState state(this);
+  state.mRenderFlags = ChangeFlag(state.mRenderFlags, aIgnore,
+                                  STATE_IGNORING_VIEWPORT_SCROLLING);
+  SetRenderingState(state);
+}
+
+void PresShell::SetDisplayPort(const nsRect& aDisplayPort)
+{
+  if (UsingDisplayPort() && mDisplayPort == aDisplayPort) {
+    return;
+  }
+  RenderingState state(this);
+  state.mRenderFlags = ChangeFlag(mRenderFlags, PR_TRUE,
+                                  STATE_USING_DISPLAYPORT);
+  state.mDisplayPort = aDisplayPort;
+  SetRenderingState(state);
+}
+
+void PresShell::SetRenderingState(const RenderingState& aState)
+{
+  if (mRenderFlags != aState.mRenderFlags) {
+    // Rendering state changed in a way that forces us to flush any
+    // retained layers we might already have.
+    LayerManager* manager = GetLayerManager();
+    if (manager) {
+      FrameLayerBuilder::InvalidateAllLayers(manager);
+    }
+  }
+
+  mRenderFlags = aState.mRenderFlags;
+  if (UsingDisplayPort()) {
+    mDisplayPort = aState.mDisplayPort;
+  } else {
+    mDisplayPort = nsRect();
+  }
+
+  nsIFrame* rootFrame = FrameManager()->GetRootFrame();
+  if (rootFrame) {
+    rootFrame->InvalidateFrameSubtree();
+  }
+}
+
 static void DrawThebesLayer(ThebesLayer* aLayer,
                             gfxContext* aContext,
                             const nsIntRegion& aRegionToDraw,
                             const nsIntRegion& aRegionToInvalidate,
                             void* aCallbackData)
 {
   PaintParams* params = static_cast<PaintParams*>(aCallbackData);
   nsIFrame* frame = params->mFrame;
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -63,16 +63,17 @@ AssertInTopLevelChromeDoc(ContainerLayer
     "Expected frame to be in top-level chrome document");
 }
 
 // Update the translation from |aContainedFrame| space to widget
 // space.  We translate because the subprocess layer manager thinks
 // it's rendering to top-left=<0, 0> (which is good!).
 static void
 SetTransformFor(ContainerLayer* aContainer, nsIFrame* aContainedFrame,
+                const FrameMetrics& aMetrics,
                 nsDisplayListBuilder* aBuilder)
 {
   NS_ABORT_IF_FALSE(aContainer && aContainedFrame, "args must be nonnull");
   AssertInTopLevelChromeDoc(aContainer, aContainedFrame);
 
   // Offset to the content rect in case we have borders or padding
   nsPoint offset = aBuilder->ToReferenceFrame(aContainedFrame->GetParent()) +
                    aContainedFrame->GetContentRect().TopLeft();
@@ -195,17 +196,17 @@ RenderFrameParent::BuildLayer(nsDisplayL
     }
     NS_ABORT_IF_FALSE(!mContainer->GetFirstChild(),
                       "container of shadow tree shouldn't have a 'root' here");
 
     mContainer->InsertAfter(shadowRoot, nsnull);
   }
 
   if (mContainer) {
-    SetTransformFor(mContainer, aFrame, aBuilder);
+    SetTransformFor(mContainer, aFrame, shadowRoot->GetFrameMetrics(), aBuilder);
     mContainer->SetClipRect(nsnull);
   }
 
   AssertValidContainerOfShadowTree(mContainer, shadowRoot);
   return nsRefPtr<Layer>(mContainer).forget();
 }
 
 void