Bug 699351. Add nsIFrameLoader::clipSubdocument API to allow XUL <iframe> and <browser> to not clip the subdocument. r=tnikkel,sr=bzbarsky
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 06 Dec 2011 01:38:46 +1300
changeset 83091 24c0a640eac11b77dc1e5088d712db7d0969e5d3
parent 83090 232990286849fe4e26658072b857e451cb368af0
child 83092 78d0b89c9800b082fb6e7f25e5f69c1c6024b132
push idunknown
push userunknown
push dateunknown
reviewerstnikkel, bzbarsky
bugs699351
milestone11.0a1
Bug 699351. Add nsIFrameLoader::clipSubdocument API to allow XUL <iframe> and <browser> to not clip the subdocument. r=tnikkel,sr=bzbarsky
content/base/public/nsIFrameLoader.idl
content/base/src/nsFrameLoader.cpp
content/base/src/nsFrameLoader.h
layout/base/nsPresShell.cpp
layout/base/tests/chrome/Makefile.in
layout/base/tests/chrome/no_clip_iframe_subdoc.html
layout/base/tests/chrome/no_clip_iframe_window.xul
layout/base/tests/chrome/test_no_clip_iframe.xul
layout/generic/nsFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIFrame.h
layout/generic/nsSubDocumentFrame.cpp
layout/generic/nsSubDocumentFrame.h
layout/generic/nsViewportFrame.cpp
--- a/content/base/public/nsIFrameLoader.idl
+++ b/content/base/public/nsIFrameLoader.idl
@@ -136,17 +136,17 @@ interface nsIContentViewManager : nsISup
                          [retval, array, size_is(aLength)] out nsIContentView aResult);
 
   /**
    * The root content view.
    */
   readonly attribute nsIContentView rootContentView;
 };
 
-[scriptable, uuid(12905a29-4246-475a-81d4-fc389197df02)]
+[scriptable, uuid(efc0b731-45dc-4189-8ffa-d3eeeb850977)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
@@ -253,16 +253,23 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * With this event mode, it's the application's responsability to 
    * convert and forward events to the content process
    */
   const unsigned long EVENT_MODE_DONT_FORWARD_TO_CHILD = 0x00000001;
 
   attribute unsigned long eventMode;
+
+  /**
+   * If false, then the subdocument is not clipped to its CSS viewport, and the
+   * subdocument's viewport scrollbar(s) are not rendered.
+   * Defaults to true.
+   */
+  attribute boolean clipSubdocument;
 };
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
 [scriptable, uuid(5879040e-83e9-40e3-b2bb-5ddf43b76e47)]
 interface nsIFrameLoaderOwner : nsISupports
 {
   /**
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -138,24 +138,22 @@ public:
     if (base_win) {
       base_win->Destroy();
     }
     return NS_OK;
   }
   nsRefPtr<nsIDocShell> mDocShell;
 };
 
-static void InvalidateFrame(nsIFrame* aFrame)
+static void InvalidateFrame(nsIFrame* aFrame, PRUint32 aFlags)
 {
+  if (!aFrame)
+    return;
   nsRect rect = nsRect(nsPoint(0, 0), aFrame->GetRect().Size());
-  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
-  // semantics the same for both in-process and out-of-process
-  // <browser>.  This is just a transform of the layer subtree in
-  // both.
-  aFrame->InvalidateWithFlags(rect, nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
+  aFrame->InvalidateWithFlags(rect, aFlags);
 }
 
 NS_IMPL_ISUPPORTS1(nsContentView, nsIContentView)
 
 bool
 nsContentView::IsRoot() const
 {
   return mScrollId == FrameMetrics::ROOT_SCROLL_ID;
@@ -184,18 +182,21 @@ nsContentView::Update(const ViewConfig& 
   }
 
   if (RenderFrameParent* rfp = mFrameLoader->GetCurrentRemoteFrame()) {
     rfp->ContentViewScaleChanged(this);
   }
 
   // XXX could be clever here and compute a smaller invalidation
   // rect
-  nsIFrame* frame = mFrameLoader->GetPrimaryFrameOfOwningContent();
-  InvalidateFrame(frame);
+  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
+  // semantics the same for both in-process and out-of-process
+  // <browser>.  This is just a transform of the layer subtree in
+  // both.
+  InvalidateFrame(mFrameLoader->GetPrimaryFrameOfOwningContent(), nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsContentView::ScrollTo(float aXpx, float aYpx)
 {
   ViewConfig config(mConfig);
   config.mScrollOffset = nsPoint(nsPresContext::CSSPixelsToAppUnits(aXpx),
@@ -323,16 +324,17 @@ nsFrameLoader::nsFrameLoader(Element* aO
   , mNeedsAsyncDestroy(false)
   , mInSwap(false)
   , mInShow(false)
   , mHideCalled(false)
   , mNetworkCreated(aNetworkCreated)
   , mDelayRemoteDialogs(false)
   , mRemoteBrowserShown(false)
   , mRemoteFrame(false)
+  , mClipSubdocument(true)
   , mCurrentRemoteFrame(nsnull)
   , mRemoteBrowser(nsnull)
   , mRenderMode(RENDER_MODE_DEFAULT)
   , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
 {
 }
 
 nsFrameLoader*
@@ -1688,17 +1690,21 @@ nsFrameLoader::GetRenderMode(PRUint32* a
 NS_IMETHODIMP
 nsFrameLoader::SetRenderMode(PRUint32 aRenderMode)
 {
   if (aRenderMode == mRenderMode) {
     return NS_OK;
   }
 
   mRenderMode = aRenderMode;
-  InvalidateFrame(GetPrimaryFrameOfOwningContent());
+  // NB: we pass INVALIDATE_NO_THEBES_LAYERS here to keep view
+  // semantics the same for both in-process and out-of-process
+  // <browser>.  This is just a transform of the layer subtree in
+  // both.
+  InvalidateFrame(GetPrimaryFrameOfOwningContent(), nsIFrame::INVALIDATE_NO_THEBES_LAYERS);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::GetEventMode(PRUint32* aEventMode)
 {
   *aEventMode = mEventMode;
   return NS_OK;
@@ -1706,16 +1712,48 @@ nsFrameLoader::GetEventMode(PRUint32* aE
 
 NS_IMETHODIMP
 nsFrameLoader::SetEventMode(PRUint32 aEventMode)
 {
   mEventMode = aEventMode;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFrameLoader::GetClipSubdocument(bool* aResult)
+{
+  *aResult = mClipSubdocument;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::SetClipSubdocument(bool aClip)
+{
+  mClipSubdocument = aClip;
+  nsIFrame* frame = GetPrimaryFrameOfOwningContent();
+  if (frame) {
+    InvalidateFrame(frame, 0);
+    frame->PresContext()->PresShell()->
+      FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+    nsSubDocumentFrame* subdocFrame = do_QueryFrame(frame);
+    if (subdocFrame) {
+      nsIFrame* subdocRootFrame = subdocFrame->GetSubdocumentRootFrame();
+      if (subdocRootFrame) {
+        nsIFrame* subdocRootScrollFrame = subdocRootFrame->PresContext()->PresShell()->
+          GetRootScrollFrame();
+        if (subdocRootScrollFrame) {
+          frame->PresContext()->PresShell()->
+            FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+        }
+      }
+    }
+  }
+  return NS_OK;
+}
+
 nsIntSize
 nsFrameLoader::GetSubDocumentSize(const nsIFrame *aIFrame)
 {
   nsSize docSizeAppUnits;
   nsPresContext* presContext = aIFrame->PresContext();
   nsCOMPtr<nsIDOMHTMLFrameElement> frameElem = 
     do_QueryInterface(aIFrame->GetContent());
   if (frameElem) {
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -282,16 +282,18 @@ public:
   {
     mCurrentRemoteFrame = aFrame;
   }
   nsFrameMessageManager* GetFrameMessageManager() { return mMessageManager; }
 
   mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; }
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
+  bool ShouldClipSubdocument() { return mClipSubdocument; }
+
 private:
 
   bool ShouldUseRemoteProcess();
 
   /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
@@ -333,17 +335,19 @@ private:
   bool mHideCalled : 1;
   // True when the object is created for an element which the parser has
   // created using NS_FROM_PARSER_NETWORK flag. If the element is modified,
   // it may lose the flag.
   bool mNetworkCreated : 1;
 
   bool mDelayRemoteDialogs : 1;
   bool mRemoteBrowserShown : 1;
-  bool mRemoteFrame;
+  bool mRemoteFrame : 1;
+  bool mClipSubdocument : 1;
+
   // XXX leaking
   nsCOMPtr<nsIObserver> mChildHost;
   RenderFrameParent* mCurrentRemoteFrame;
   TabParent* mRemoteBrowser;
 
   // See nsIFrameLoader.idl.  Short story, if !(mRenderMode &
   // RENDER_MODE_ASYNC_SCROLL), all the fields below are ignored in
   // favor of what content tells.
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -7290,24 +7290,24 @@ PresShell::DoReflow(nsIFrame* target, bo
   // root frame, then its desired size had better not change!  If it's
   // initiated at the root, then the size better not change unless its
   // height was unconstrained to start with.
   NS_ASSERTION((target == rootFrame && size.height == NS_UNCONSTRAINEDSIZE) ||
                (desiredSize.width == size.width &&
                 desiredSize.height == size.height),
                "non-root frame's desired size changed during an "
                "incremental reflow");
-  NS_ASSERTION(desiredSize.VisualOverflow().IsEqualInterior(
+  NS_ASSERTION(target == rootFrame || desiredSize.VisualOverflow().IsEqualInterior(
                  nsRect(nsPoint(0, 0),
                         nsSize(desiredSize.width, desiredSize.height))),
-               "reflow roots must not have visible overflow");
-  NS_ASSERTION(desiredSize.ScrollableOverflow().IsEqualEdges(
+               "non-root reflow roots must not have visible overflow");
+  NS_ASSERTION(target == rootFrame || desiredSize.ScrollableOverflow().IsEqualEdges(
                  nsRect(nsPoint(0, 0),
                         nsSize(desiredSize.width, desiredSize.height))),
-               "reflow roots must not have scrollable overflow");
+               "non-root reflow roots must not have scrollable overflow");
   NS_ASSERTION(status == NS_FRAME_COMPLETE,
                "reflow roots should never split");
 
   target->SetSize(nsSize(desiredSize.width, desiredSize.height));
 
   nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, target,
                                              target->GetView(),
                                              desiredSize.VisualOverflow());
--- a/layout/base/tests/chrome/Makefile.in
+++ b/layout/base/tests/chrome/Makefile.in
@@ -57,16 +57,19 @@ include $(topsrcdir)/config/rules.mk
 	bug551434_childframe.html \
 	test_chrome_content_integration.xul \
 	     chrome_content_integration_window.xul \
 	test_chrome_over_plugin.xul \
 	     chrome_over_plugin_window.xul \
 	test_default_background.xul \
 	     default_background_window.xul \
     test_leaf_layers_partition_browser_window.xul \
+    test_no_clip_iframe.xul \
+         no_clip_iframe_window.xul \
+         no_clip_iframe_subdoc.html \
 	test_printpreview.xul \
 	     printpreview_helper.xul \
 	test_printpreview_bug396024.xul \
 	     printpreview_bug396024_helper.xul \
 	test_printpreview_bug482976.xul \
 	     printpreview_bug482976_helper.xul \
     $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/chrome/no_clip_iframe_subdoc.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<body style="margin:0; background:lime;">
+<div id="d" style="position:relative; top:-50px; width:150px; height:250px; background:yellow;"></div>
+<div id="p" style="margin-top:-50px; width:150px; height:50px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/chrome/no_clip_iframe_window.xul
@@ -0,0 +1,137 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTests()">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+  <div id="container" xmlns="http://www.w3.org/1999/xhtml" style="height:400px; overflow:auto; background:gray">
+    <div style="height:0">
+      <iframe type="content" id="f" src="no_clip_iframe_subdoc.html"
+       style="margin-top:50px; border:1px solid black; width:100px; height:100px;"/>
+    </div>
+    <div id="ref" style="background:gray;">
+      <div style="border:1px solid black; margin-top:50px; width:100px; height:100px;">
+        <div id="ref-d" style="background:lime; height:250px; width:150px;">
+          <div style="position:relative; top:-50px; width:150px; height:100%; background:yellow;"/>
+        </div>
+      </div>
+    </div>
+  </div>
+  <vbox flex="1"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+    var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ];
+    for each (var name in imports) {
+      window[name] = window.opener.wrappedJSObject[name];
+    }
+
+    SimpleTest.waitForExplicitFinish();
+
+    var accumulatedRect = null;
+    var onpaint = function() {};
+
+    function paintListener(event) {
+      if (event.target != window)
+        return;
+      dump("got MozAfterPaint: " + event.boundingClientRect.left + "," + event.boundingClientRect.top + "," +
+           event.boundingClientRect.right + "," + event.boundingClientRect.bottom + "\n");
+      if (accumulatedRect) {
+        accumulatedRect[0] = Math.min(accumulatedRect[0], event.boundingClientRect.left);
+        accumulatedRect[1] = Math.min(accumulatedRect[1], event.boundingClientRect.top);
+        accumulatedRect[2] = Math.max(accumulatedRect[2], event.boundingClientRect.right);
+        accumulatedRect[3] = Math.max(accumulatedRect[3], event.boundingClientRect.bottom);
+      } else {
+        accumulatedRect = [event.boundingClientRect.left, event.boundingClientRect.top,
+                           event.boundingClientRect.right, event.boundingClientRect.bottom];
+      }
+      onpaint();
+    }
+    window.addEventListener("MozAfterPaint", paintListener, false);
+
+    function waitForAllPaintsFlushed(callback) {
+      document.documentElement.getBoundingClientRect();
+      var CI = Components.interfaces;
+      var utils = window.QueryInterface(CI.nsIInterfaceRequestor)
+                  .getInterface(CI.nsIDOMWindowUtils);
+      if (!utils.isMozAfterPaintPending) {
+        dump("done...\n");
+        var result = accumulatedRect;
+        accumulatedRect = null;
+        onpaint = function() {};
+        if (!result) {
+          result = [0,0,0,0];
+        }
+        callback(result[0], result[1], result[2], result[3]);
+        return;
+      }
+      dump("waiting for paint...\n");
+      onpaint = function() { waitForAllPaintsFlushed(callback); };
+    }
+
+    var Ci = Components.interfaces;
+    var frame = document.getElementById("f");
+    var fl = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+    is(fl.clipSubdocument, true, "clipSubdocument should default to true");
+    fl.clipSubdocument = false;
+    is(fl.clipSubdocument, false, "clipSubdocument should have been set to false");
+
+    function runTests() {
+      var ref = document.getElementById("ref");
+      frame.contentWindow.scrollTo(0,0);
+
+      ref.style.visibility = "hidden";
+      var testCanvas = snapshotWindow(window);
+      ref.style.visibility = "";
+      var refCanvas = snapshotWindow(window);
+      var comparison = compareSnapshots(testCanvas, refCanvas, true);
+      ok(comparison[0], "Basic overflow drawing; got " + comparison[1] + ", expected " + comparison[2]);
+
+      document.getElementById("container").style.height = "200px";
+      ref.style.visibility = "hidden";
+      testCanvas = snapshotWindow(window);
+      ref.style.visibility = "";
+      refCanvas = snapshotWindow(window);
+      comparison = compareSnapshots(testCanvas, refCanvas, true);
+      ok(comparison[0], "Drawing with vertical scrollbar to show overflow area computation; got " +
+                        comparison[1] + ", expected " + comparison[2]);
+
+      frame.contentDocument.getElementById("d").style.height = "350px";
+      document.getElementById("ref-d").style.height = "350px";
+      ref.style.visibility = "hidden";
+      testCanvas = snapshotWindow(window);
+      ref.style.visibility = "";
+      refCanvas = snapshotWindow(window);
+      comparison = compareSnapshots(testCanvas, refCanvas, true);
+      ok(comparison[0], "testing dynamic overflow area change affecting scrollbar; got " +
+                        comparison[1] + ", expected " + comparison[2]);
+
+      // Now do invalidation tests
+      ref.style.visibility = "hidden";
+      document.getElementById("container").style.height = "400px";
+      waitForAllPaintsFlushed(function() {
+        dump("Scrolling\n");
+        frame.contentWindow.scrollTo(0,80);
+        waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+          ok(x1 <= 1 && x2 >= 151 && y1 <= 0 && y2 >= 400,
+             "Entire scrolled region is painted: " + x1 + "," + y1 + "," + x2 + "," + y2);
+          frame.contentDocument.getElementById("p").style.background = "cyan";
+          waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+            ok(x1 <= 1 && x2 >= 151 && y1 <= 271 && y2 >= 320,
+               "Entire updated region is painted: " + x1 + "," + y1 + "," + x2 + "," + y2);
+      
+            var tester = window.SimpleTest;
+            window.close();
+            tester.finish();
+          });
+        });
+      });
+    }
+  ]]>
+  </script>
+</window>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/chrome/test_no_clip_iframe.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+    SimpleTest.waitForExplicitFinish();
+    // Run the test in a separate window so that the test runs as a chrome
+    // window
+    window.open("no_clip_iframe_window.xul", "no_clip_iframe",
+                "chrome,width=200,height=400");
+  ]]>
+  </script>
+</window>
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -6434,23 +6434,27 @@ nsFrame::CreateAccessible()
 {
   return nsnull;
 }
 #endif
 
 NS_DECLARE_FRAME_PROPERTY(OverflowAreasProperty,
                           nsIFrame::DestroyOverflowAreas)
 
-void
+bool
 nsIFrame::ClearOverflowRects()
 {
+  if (mOverflow.mType == NS_FRAME_OVERFLOW_NONE) {
+    return false;
+  }
   if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
     Properties().Delete(OverflowAreasProperty());
   }
   mOverflow.mType = NS_FRAME_OVERFLOW_NONE;
+  return true;
 }
 
 /** Create or retrieve the previously stored overflow area, if the frame does 
  * not overflow and no creation is required return nsnull.
  * @return pointer to the overflow area rectangle 
  */
 nsOverflowAreas*
 nsIFrame::GetOverflowAreasProperty()
@@ -6468,27 +6472,28 @@ nsIFrame::GetOverflowAreasProperty()
   overflow = new nsOverflowAreas;
   props.Set(OverflowAreasProperty(), overflow);
   return overflow;
 }
 
 /** Set the overflowArea rect, storing it as deltas or a separate rect
  * depending on its size in relation to the primary frame rect.
  */
-void
+bool
 nsIFrame::SetOverflowAreas(const nsOverflowAreas& aOverflowAreas)
 {
   if (mOverflow.mType == NS_FRAME_OVERFLOW_LARGE) {
     nsOverflowAreas *overflow =
       static_cast<nsOverflowAreas*>(Properties().Get(OverflowAreasProperty()));
+    bool changed = *overflow != aOverflowAreas;
     *overflow = aOverflowAreas;
 
     // Don't bother with converting to the deltas form if we already
     // have a property.
-    return;
+    return changed;
   }
 
   const nsRect& vis = aOverflowAreas.VisualOverflow();
   PRUint32 l = -vis.x, // left edge: positive delta is leftwards
            t = -vis.y, // top: positive is upwards
            r = vis.XMost() - mRect.width, // right: positive is rightwards
            b = vis.YMost() - mRect.height; // bottom: positive is downwards
   if (aOverflowAreas.ScrollableOverflow().IsEqualEdges(nsRect(nsPoint(0, 0), GetSize())) &&
@@ -6500,41 +6505,48 @@ nsIFrame::SetOverflowAreas(const nsOverf
       // set a frame as having no overflow in this function.  This is
       // because FinishAndStoreOverflow calls this function prior to
       // SetRect based on whether the overflow areas match aNewSize.
       // In the case where the overflow areas exactly match mRect but
       // do not match aNewSize, we need to store overflow in a property
       // so that our eventual SetRect/SetSize will know that it has to
       // reset our overflow areas.
       (l | t | r | b) != 0) {
+    VisualDeltas oldDeltas = mOverflow.mVisualDeltas;
     // It's a "small" overflow area so we store the deltas for each edge
     // directly in the frame, rather than allocating a separate rect.
     // If they're all zero, that's fine; we're setting things to
     // no-overflow.
     mOverflow.mVisualDeltas.mLeft   = l;
     mOverflow.mVisualDeltas.mTop    = t;
     mOverflow.mVisualDeltas.mRight  = r;
     mOverflow.mVisualDeltas.mBottom = b;
+    // There was no scrollable overflow before, and there isn't now.
+    return oldDeltas != mOverflow.mVisualDeltas;
   } else {
+    bool changed = !aOverflowAreas.ScrollableOverflow().IsEqualEdges(nsRect(nsPoint(0, 0), GetSize())) ||
+      !aOverflowAreas.VisualOverflow().IsEqualEdges(GetVisualOverflowFromDeltas());
+
     // it's a large overflow area that we need to store as a property
     mOverflow.mType = NS_FRAME_OVERFLOW_LARGE;
     nsOverflowAreas* overflow = GetOverflowAreasProperty();
     NS_ASSERTION(overflow, "should have created areas");
     *overflow = aOverflowAreas;
+    return changed;
   }
 }
 
 inline bool
 IsInlineFrame(nsIFrame *aFrame)
 {
   nsIAtom *type = aFrame->GetType();
   return type == nsGkAtoms::inlineFrame;
 }
 
-void 
+bool
 nsIFrame::FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
                                  nsSize aNewSize)
 {
   nsRect bounds(nsPoint(0, 0), aNewSize);
   // Store the passed in overflow area if we are a preserve-3d frame,
   // and it's not just the frame bounds.
   if (Preserves3D() && (!aOverflowAreas.VisualOverflow().IsEqualEdges(bounds) ||
                         !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds))) {
@@ -6633,21 +6645,21 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
     }
     if (Preserves3DChildren()) {
       ComputePreserve3DChildrenOverflow(aOverflowAreas, newBounds);
     }
   }
 
   bool visualOverflowChanged =
     !GetVisualOverflowRect().IsEqualInterior(aOverflowAreas.VisualOverflow());
-
+  bool anyOverflowChanged;
   if (aOverflowAreas != nsOverflowAreas(bounds, bounds)) {
-    SetOverflowAreas(aOverflowAreas);
+    anyOverflowChanged = SetOverflowAreas(aOverflowAreas);
   } else {
-    ClearOverflowRects();
+    anyOverflowChanged = ClearOverflowRects();
   }
 
   if (visualOverflowChanged) {
     if (hasOutlineOrEffects) {
       // When there's an outline or box-shadow or SVG effects,
       // changes to those styles might require repainting of the old and new
       // overflow areas. Repainting of the old overflow area is handled in
       // nsCSSFrameConstructor::DoApplyRenderingChangeToTree in response
@@ -6682,16 +6694,18 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
       // If there is no transform now, then the container layer for
       // the transform will go away and the frame contents will change
       // ThebesLayers, forcing it to be invalidated, so it doesn't matter
       // that we didn't reach here.
       InvalidateLayer(aOverflowAreas.VisualOverflow(),
                       nsDisplayItem::TYPE_TRANSFORM);
     }
   }
+
+  return anyOverflowChanged;
 }
 
 /* The overflow rects for leaf nodes in a preserve-3d hierarchy depends on
  * the mRect value for their parents (since we use their transform, and transform
  * depends on this for transform-origin etc). These weren't necessarily correct
  * when we reflowed initially, so walk over all preserve-3d children and repeat the
  * overflow calculation.
  */
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -77,16 +77,17 @@
 #endif
 #include "nsBidiUtils.h"
 #include "nsFrameManager.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/dom/Element.h"
 #include "FrameLayerBuilder.h"
 #include "nsSMILKeySpline.h"
+#include "nsSubDocumentFrame.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 
 //----------nsHTMLScrollFrame-------------------------------------------
 
@@ -191,26 +192,30 @@ nsHTMLScrollFrame::InvalidateInternal(co
                                       nscoord aX, nscoord aY, nsIFrame* aForChild,
                                       PRUint32 aFlags)
 {
   if (aForChild) {
     if (aForChild == mInner.mScrolledFrame) {
       nsRect damage = aDamageRect + nsPoint(aX, aY);
       // This is the damage rect that we're going to pass up to our parent.
       nsRect parentDamage;
-      // If we're using a displayport, we might be displaying an area
-      // different than our scroll port and the damage needs to be
-      // clipped to that instead.
-      nsRect displayport;
-      bool usingDisplayport = nsLayoutUtils::GetDisplayPort(GetContent(),
-                                                              &displayport);
-      if (usingDisplayport) {
-        parentDamage.IntersectRect(damage, displayport);
+      if (mInner.IsIgnoringViewportClipping()) {
+        parentDamage = damage;
       } else {
-        parentDamage.IntersectRect(damage, mInner.mScrollPort);
+        // If we're using a displayport, we might be displaying an area
+        // different than our scroll port and the damage needs to be
+        // clipped to that instead.
+        nsRect displayport;
+        bool usingDisplayport = nsLayoutUtils::GetDisplayPort(GetContent(),
+                                                                &displayport);
+        if (usingDisplayport) {
+          parentDamage.IntersectRect(damage, displayport);
+        } else {
+          parentDamage.IntersectRect(damage, mInner.mScrollPort);
+        }
       }
 
       if (IsScrollingActive()) {
         // This is the damage rect that we're going to pass up and
         // only request invalidation of ThebesLayers for.
         // damage is now in our coordinate system, which means it was
         // translated using the current scroll position. Adjust it to
         // reflect the scroll position at last paint, since that's what
@@ -278,17 +283,17 @@ nsHTMLScrollFrame::InvalidateInternal(co
 
 struct ScrollReflowState {
   const nsHTMLReflowState& mReflowState;
   nsBoxLayoutState mBoxState;
   nsGfxScrollFrameInner::ScrollbarStyles mStyles;
   nsMargin mComputedBorder;
 
   // === Filled in by ReflowScrolledFrame ===
-  nsRect mContentsOverflowArea;
+  nsOverflowAreas mContentsOverflowAreas;
   bool mReflowedContentsWithHScrollbar;
   bool mReflowedContentsWithVScrollbar;
 
   // === Filled in when TryLayout succeeds ===
   // The size of the inside-border area
   nsSize mInsideBorderSize;
   // Whether we decided to show the horizontal scrollbar
   bool mShowHScrollbar;
@@ -422,17 +427,18 @@ nsHTMLScrollFrame::TryLayout(ScrollReflo
     NS_MAX(aKidMetrics->height, vScrollbarMinHeight);
   aState->mInsideBorderSize =
     ComputeInsideBorderSize(aState, desiredInsideBorderSize);
   nsSize scrollPortSize = nsSize(NS_MAX(0, aState->mInsideBorderSize.width - vScrollbarDesiredWidth),
                                  NS_MAX(0, aState->mInsideBorderSize.height - hScrollbarDesiredHeight));
                                                                                 
   if (!aForce) {
     nsRect scrolledRect =
-      mInner.GetScrolledRectInternal(aState->mContentsOverflowArea, scrollPortSize);
+      mInner.GetScrolledRectInternal(aState->mContentsOverflowAreas.ScrollableOverflow(),
+                                     scrollPortSize);
     nscoord oneDevPixel = aState->mBoxState.PresContext()->DevPixelsToAppUnits(1);
 
     // If the style is HIDDEN then we already know that aAssumeHScroll is false
     if (aState->mStyles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
       bool wantHScrollbar =
         aState->mStyles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
         scrolledRect.XMost() >= scrollPortSize.width + oneDevPixel ||
         scrolledRect.x <= -oneDevPixel;
@@ -561,17 +567,17 @@ nsHTMLScrollFrame::ReflowScrolledFrame(S
   // setting their mOverflowArea. This is wrong because every frame should
   // always set mOverflowArea. In fact nsObjectFrame and nsFrameFrame don't
   // support the 'outline' property because of this. Rather than fix the world
   // right now, just fix up the overflow area if necessary. Note that we don't
   // check HasOverflowRect() because it could be set even though the
   // overflow area doesn't include the frame bounds.
   aMetrics->UnionOverflowAreasWithDesiredBounds();
 
-  aState->mContentsOverflowArea = aMetrics->ScrollableOverflow();
+  aState->mContentsOverflowAreas = aMetrics->mOverflowAreas;
   aState->mReflowedContentsWithHScrollbar = aAssumeHScroll;
   aState->mReflowedContentsWithVScrollbar = aAssumeVScroll;
   
   return rv;
 }
 
 bool
 nsHTMLScrollFrame::GuessHScrollbarNeeded(const ScrollReflowState& aState)
@@ -713,17 +719,19 @@ nsHTMLScrollFrame::PlaceScrollArea(const
 {
   nsIFrame *scrolledFrame = mInner.mScrolledFrame;
   // Set the x,y of the scrolled frame to the correct value
   scrolledFrame->SetPosition(mInner.mScrollPort.TopLeft() - aScrollPosition);
 
   nsRect scrolledArea;
   // Preserve the width or height of empty rects
   nsSize portSize = mInner.mScrollPort.Size();
-  nsRect scrolledRect = mInner.GetScrolledRectInternal(aState.mContentsOverflowArea, portSize);
+  nsRect scrolledRect =
+    mInner.GetScrolledRectInternal(aState.mContentsOverflowAreas.ScrollableOverflow(),
+                                   portSize);
   scrolledArea.UnionRectEdges(scrolledRect,
                               nsRect(nsPoint(0,0), portSize));
 
   // Store the new overflow area. Note that this changes where an outline
   // of the scrolled frame would be painted, but scrolled frames can't have
   // outlines (the outline would go on this scrollframe instead).
   // Using FinishAndStoreOverflow is needed so the overflow rect
   // gets set correctly.  It also messes with the overflow rect in the
@@ -920,16 +928,20 @@ nsHTMLScrollFrame::Reflow(nsPresContext*
   }
 
   aDesiredSize.width = state.mInsideBorderSize.width +
     state.mComputedBorder.LeftRight();
   aDesiredSize.height = state.mInsideBorderSize.height +
     state.mComputedBorder.TopBottom();
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
+  if (mInner.IsIgnoringViewportClipping()) {
+    aDesiredSize.mOverflowAreas.UnionWith(
+      state.mContentsOverflowAreas + mInner.mScrolledFrame->GetPosition());
+  }
 
   CheckInvalidateSizeChange(aDesiredSize);
 
   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
 
   if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
     mInner.mHadNonInitialReflow = true;
   }
@@ -1682,16 +1694,25 @@ InvalidateFixedBackgroundFrames(nsIFrame
 
   nsRegion visibleRegion(aUpdateRect);
   list.ComputeVisibilityForRoot(&builder, &visibleRegion);
 
   InvalidateFixedBackgroundFramesFromList(&builder, aMovingFrame, list);
   list.DeleteAll();
 }
 
+bool nsGfxScrollFrameInner::IsIgnoringViewportClipping() const
+{
+  if (!mIsRoot)
+    return false;
+  nsSubDocumentFrame* subdocFrame = static_cast<nsSubDocumentFrame*>
+    (nsLayoutUtils::GetCrossDocParentFrame(mOuter->PresContext()->PresShell()->GetRootFrame()));
+  return subdocFrame && !subdocFrame->ShouldClipSubdocument();
+}
+
 bool nsGfxScrollFrameInner::IsAlwaysActive() const
 {
   // The root scrollframe for a non-chrome document which is the direct
   // child of a chrome document is always treated as "active".
   // XXX maybe we should extend this so that IFRAMEs which are fill the
   // entire viewport (like GMail!) are always active
   return mIsRoot && mOuter->PresContext()->IsRootContentDocument();
 }
@@ -1716,17 +1737,17 @@ void nsGfxScrollFrameInner::MarkActive()
   } else {
     if (!gScrollFrameActivityTracker) {
       gScrollFrameActivityTracker = new ScrollFrameActivityTracker();
     }
     gScrollFrameActivityTracker->AddObject(this);
   }
 }
 
-void nsGfxScrollFrameInner::ScrollVisual()
+void nsGfxScrollFrameInner::ScrollVisual(nsPoint aOldScrolledFramePos)
 {
   nsRootPresContext* rootPresContext = mOuter->PresContext()->GetRootPresContext();
   if (!rootPresContext) {
     return;
   }
 
   rootPresContext->RequestUpdatePluginGeometry(mOuter);
 
@@ -1743,19 +1764,25 @@ void nsGfxScrollFrameInner::ScrollVisual
       flags |= nsIFrame::INVALIDATE_NO_THEBES_LAYERS;
     }
   }
   if (canScrollWithBlitting) {
     MarkActive();
   }
 
   nsRect invalidateRect, displayport;
-  invalidateRect =
-    (nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayport)) ?
-    displayport : mScrollPort;
+  if (IsIgnoringViewportClipping()) {
+    nsRect visualOverflow = mScrolledFrame->GetVisualOverflowRect();
+    invalidateRect.UnionRect(visualOverflow + mScrolledFrame->GetPosition(),
+            visualOverflow + aOldScrolledFramePos);
+  } else {
+    invalidateRect =
+      (nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayport)) ?
+      displayport : mScrollPort;
+  }
 
   mOuter->InvalidateWithFlags(invalidateRect, flags);
 
   if (flags & nsIFrame::INVALIDATE_NO_THEBES_LAYERS) {
     nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(mOuter);
     nsRect update =
       GetScrollPortRect() + mOuter->GetOffsetToCrossDoc(displayRoot);
     update = update.ConvertAppUnitsRoundOut(
@@ -1815,22 +1842,23 @@ nsGfxScrollFrameInner::ScrollToImpl(nsPo
                "curPos.x not a multiple of device pixels");
   NS_ASSERTION(curPosDevPx.y*appUnitsPerDevPixel == curPos.y,
                "curPos.y not a multiple of device pixels");
 
   // notify the listeners.
   for (PRUint32 i = 0; i < mListeners.Length(); i++) {
     mListeners[i]->ScrollPositionWillChange(pt.x, pt.y);
   }
-  
+
+  nsPoint oldScrollFramePos = mScrolledFrame->GetPosition();
   // Update frame position for scrolling
   mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
 
   // We pass in the amount to move visually
-  ScrollVisual();
+  ScrollVisual(oldScrollFramePos);
 
   presContext->PresShell()->SynthesizeMouseMove(true);
   UpdateScrollbarPosition();
   PostScrollEvent();
 
   // notify the listeners.
   for (PRUint32 i = 0; i < mListeners.Length(); i++) {
     mListeners[i]->ScrollPositionDidChange(pt.x, pt.y);
@@ -1932,17 +1960,17 @@ nsGfxScrollFrameInner::BuildDisplayList(
 
   if (aBuilder->IsPaintingToWindow()) {
     mScrollPosAtLastPaint = GetScrollPosition();
     if (IsScrollingActive() && !CanScrollWithBlitting(mOuter)) {
       MarkInactive();
     }
   }
 
-  if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
+  if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) {
     // Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
     // The scrolled frame shouldn't have its own background/border, so we
     // can just pass aLists directly.
     return mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame,
                                             aDirtyRect, aLists);
   }
 
   // We put scrollbars in their own layers when this is the root scroll
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -177,17 +177,17 @@ public:
   }
   nsRect GetScrollRange() const;
 
   nsPoint ClampAndRestrictToDevPixels(const nsPoint& aPt, nsIntPoint* aPtDevPx) const;
   nsPoint ClampScrollPosition(const nsPoint& aPt) const;
   static void AsyncScrollCallback(nsITimer *aTimer, void* anInstance);
   void ScrollTo(nsPoint aScrollPosition, nsIScrollableFrame::ScrollMode aMode);
   void ScrollToImpl(nsPoint aScrollPosition);
-  void ScrollVisual();
+  void ScrollVisual(nsPoint aOldScrolledFramePosition);
   void ScrollBy(nsIntPoint aDelta, nsIScrollableFrame::ScrollUnit aUnit,
                 nsIScrollableFrame::ScrollMode aMode, nsIntPoint* aOverflow);
   void ScrollToRestoredPosition();
   nsSize GetLineScrollAmount() const;
   nsSize GetPageScrollAmount() const;
 
   nsPresState* SaveState(nsIStatefulFrame::SpecialStateID aStateID);
   void RestoreState(nsPresState* aState);
@@ -251,16 +251,18 @@ public:
   void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
                                      nsRect& aRect, bool aHasResizer, bool aVertical);
   // returns true if a resizer should be visible
   bool HasResizer() { return mResizerBox && !mCollapsedResizer; }
   void LayoutScrollbars(nsBoxLayoutState& aState,
                         const nsRect& aContentArea,
                         const nsRect& aOldScrollArea);
 
+  bool IsIgnoringViewportClipping() const;
+
   bool IsAlwaysActive() const;
   void MarkActive();
   void MarkInactive();
   nsExpirationState* GetExpirationState() { return &mActivityExpirationState; }
 
   // owning references to the nsIAnonymousContentCreator-built content
   nsCOMPtr<nsIContent> mHScrollbarContent;
   nsCOMPtr<nsIContent> mVScrollbarContent;
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2269,38 +2269,40 @@ public:
    * @return the rect relative to this frame, before any CSS transforms have
    * been applied, i.e. in this frame's coordinate system
    */
   nsRect GetVisualOverflowRectRelativeToSelf() const;
 
   /**
    * Store the overflow area in the frame's mOverflow.mVisualDeltas
    * fields or as a frame property in the frame manager so that it can
-   * be retrieved later without reflowing the frame.
+   * be retrieved later without reflowing the frame. Returns true if either of
+   * the overflow areas changed.
    */
-  void FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
+  bool FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
                               nsSize aNewSize);
 
-  void FinishAndStoreOverflow(nsHTMLReflowMetrics* aMetrics) {
-    FinishAndStoreOverflow(aMetrics->mOverflowAreas,
-                           nsSize(aMetrics->width, aMetrics->height));
+  bool FinishAndStoreOverflow(nsHTMLReflowMetrics* aMetrics) {
+    return FinishAndStoreOverflow(aMetrics->mOverflowAreas,
+                                  nsSize(aMetrics->width, aMetrics->height));
   }
 
   /**
    * Returns whether the frame has an overflow rect that is different from
    * its border-box.
    */
   bool HasOverflowAreas() const {
     return mOverflow.mType != NS_FRAME_OVERFLOW_NONE;
   }
 
   /**
    * Removes any stored overflow rects (visual and scrollable) from the frame.
+   * Returns true if the overflow changed.
    */
-  void ClearOverflowRects();
+  bool ClearOverflowRects();
 
   /**
    * Determine whether borders should not be painted on certain sides of the
    * frame.
    */
   virtual PRIntn GetSkipSides() const { return 0; }
 
   /** Selection related calls
@@ -2817,24 +2819,34 @@ protected:
   // we store a set of four 1-byte deltas from the edges of mRect
   // rather than allocating a whole separate rectangle property.
   // Note that these are unsigned values, all measured "outwards"
   // from the edges of mRect, so /mLeft/ and /mTop/ are reversed from
   // our normal coordinate system.
   // If mOverflow.mType == NS_FRAME_OVERFLOW_LARGE, then the
   // delta values are not meaningful and the overflow area is stored
   // as a separate rect property.
+  struct VisualDeltas {
+    PRUint8 mLeft;
+    PRUint8 mTop;
+    PRUint8 mRight;
+    PRUint8 mBottom;
+    bool operator==(const VisualDeltas& aOther) const
+    {
+      return mLeft == aOther.mLeft && mTop == aOther.mTop &&
+             mRight == aOther.mRight && mBottom == aOther.mBottom;
+    }
+    bool operator!=(const VisualDeltas& aOther) const
+    {
+      return !(*this == aOther);
+    }
+  };
   union {
-    PRUint32  mType;
-    struct {
-      PRUint8 mLeft;
-      PRUint8 mTop;
-      PRUint8 mRight;
-      PRUint8 mBottom;
-    } mVisualDeltas;
+    PRUint32     mType;
+    VisualDeltas mVisualDeltas;
   } mOverflow;
 
   // Helpers
   /**
    * For frames that have top-level windows (top-level viewports,
    * comboboxes, menupoups) this function will invalidate the window.
    */
   void InvalidateRoot(const nsRect& aDamageRect, PRUint32 aFlags);
@@ -2939,17 +2951,20 @@ private:
     // to cast away the unsigned-ness.
     return nsRect(-(PRInt32)mOverflow.mVisualDeltas.mLeft,
                   -(PRInt32)mOverflow.mVisualDeltas.mTop,
                   mRect.width + mOverflow.mVisualDeltas.mRight +
                                 mOverflow.mVisualDeltas.mLeft,
                   mRect.height + mOverflow.mVisualDeltas.mBottom +
                                  mOverflow.mVisualDeltas.mTop);
   }
-  void SetOverflowAreas(const nsOverflowAreas& aOverflowAreas);
+  /**
+   * Returns true if any overflow changed.
+   */
+  bool SetOverflowAreas(const nsOverflowAreas& aOverflowAreas);
   nsPoint GetOffsetToCrossDoc(const nsIFrame* aOther, const PRInt32 aAPD) const;
 
 #ifdef NS_DEBUG
 public:
   // Formerly nsIFrameDebug
   NS_IMETHOD  List(FILE* out, PRInt32 aIndent) const = 0;
   NS_IMETHOD  GetFrameName(nsAString& aResult) const = 0;
   NS_IMETHOD_(nsFrameState)  GetDebugStateBits() const = 0;
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -406,28 +406,31 @@ nsSubDocumentFrame::BuildDisplayList(nsD
     
     if (!addedLayer && presContext->IsRootContentDocument()) {
       // We always want top level content documents to be in their own layer.
       nsDisplayOwnLayer* layerItem = new (aBuilder) nsDisplayOwnLayer(
         aBuilder, subdocRootFrame ? subdocRootFrame : this, &childItems);
       childItems.AppendToTop(layerItem);
     }
 
-    nsDisplayList list;
-    // Clip children to the child root frame's rectangle
-    rv = list.AppendNewToTop(
+    if (ShouldClipSubdocument()) {
+      nsDisplayClip* item =
         new (aBuilder) nsDisplayClip(aBuilder, this, &childItems,
-                                     subdocBoundsInParentUnits));
+                                     subdocBoundsInParentUnits);
+      // Clip children to the child root frame's rectangle
+      childItems.AppendToTop(item);
+    }
 
     if (mIsInline) {
-      WrapReplacedContentForBorderRadius(aBuilder, &list, aLists);
+      WrapReplacedContentForBorderRadius(aBuilder, &childItems, aLists);
     } else {
-      aLists.Content()->AppendToTop(&list);
+      aLists.Content()->AppendToTop(&childItems);
     }
   }
+
   // delete childItems in case of OOM
   childItems.DeleteAll();
 
   if (subdocRootFrame) {
     aBuilder->LeavePresShell(subdocRootFrame, dirty);
   }
 
   return rv;
@@ -612,16 +615,24 @@ nsSubDocumentFrame::Reflow(nsPresContext
   }
 
   if (mInnerView) {
     nsIViewManager* vm = mInnerView->GetViewManager();
     vm->MoveViewTo(mInnerView, offset.x, offset.y);
     vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), innerSize), true);
   }
 
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+  if (!ShouldClipSubdocument()) {
+    nsIFrame* subdocRootFrame = GetSubdocumentRootFrame();
+    if (subdocRootFrame) {
+      aDesiredSize.mOverflowAreas.UnionWith(subdocRootFrame->GetOverflowAreas() + offset);
+    }
+  }
+
   // Determine if we need to repaint our border, background or outline
   CheckInvalidateSizeChange(aDesiredSize);
 
   FinishAndStoreOverflow(&aDesiredSize);
 
   if (!aPresContext->IsPaginated() && !mPostedReflowCallback) {
     PresContext()->PresShell()->PostReflowCallback(this);
     mPostedReflowCallback = true;
--- a/layout/generic/nsSubDocumentFrame.h
+++ b/layout/generic/nsSubDocumentFrame.h
@@ -119,16 +119,22 @@ public:
   void EndSwapDocShells(nsIFrame* aOther);
   nsIView* EnsureInnerView();
   nsIFrame* GetSubdocumentRootFrame();
 
   // nsIReflowCallback
   virtual bool ReflowFinished();
   virtual void ReflowCallbackCanceled();
 
+  bool ShouldClipSubdocument()
+  {
+    nsFrameLoader* frameLoader = FrameLoader();
+    return !frameLoader || frameLoader->ShouldClipSubdocument();
+  }
+
 protected:
   friend class AsyncFrameInit;
 
   // Helper method to look up the HTML marginwidth & marginheight attributes
   nsIntSize GetMarginAttributes();
 
   nsFrameLoader* FrameLoader();
 
--- a/layout/generic/nsViewportFrame.cpp
+++ b/layout/generic/nsViewportFrame.cpp
@@ -42,16 +42,17 @@
 
 #include "nsCOMPtr.h"
 #include "nsViewportFrame.h"
 #include "nsHTMLParts.h"
 #include "nsGkAtoms.h"
 #include "nsIScrollableFrame.h"
 #include "nsDisplayList.h"
 #include "FrameLayerBuilder.h"
+#include "nsSubDocumentFrame.h"
 #include "nsAbsoluteContainingBlock.h"
 
 using namespace mozilla;
 
 nsIFrame*
 NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) ViewportFrame(aContext);
@@ -205,16 +206,18 @@ ViewportFrame::Reflow(nsPresContext*    
  
   // Reflow the main content first so that the placeholders of the
   // fixed-position frames will be in the right places on an initial
   // reflow.
   nscoord kidHeight = 0;
 
   nsresult rv = NS_OK;
   
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+
   if (mFrames.NotEmpty()) {
     // Deal with a non-incremental reflow or an incremental reflow
     // targeted at our one-and-only principal child frame.
     if (aReflowState.ShouldReflowAllKids() ||
         aReflowState.mFlags.mVResize ||
         NS_SUBTREE_DIRTY(mFrames.FirstChild())) {
       // Reflow our one-and-only principal child frame
       nsIFrame*           kidFrame = mFrames.FirstChild();
@@ -229,16 +232,17 @@ ViewportFrame::Reflow(nsPresContext*    
       rv = ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowState,
                        0, 0, 0, aStatus);
       kidHeight = kidDesiredSize.height;
 
       FinishReflowChild(kidFrame, aPresContext, nsnull, kidDesiredSize, 0, 0, 0);
     } else {
       kidHeight = mFrames.FirstChild()->GetSize().height;
     }
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, mFrames.FirstChild());
   }
 
   NS_ASSERTION(aReflowState.availableWidth != NS_UNCONSTRAINEDSIZE,
                "shouldn't happen anymore");
 
   // Return the max size as our desired size
   aDesiredSize.width = aReflowState.availableWidth;
   // Being flowed initially at an unconstrained height means we should
@@ -247,42 +251,49 @@ ViewportFrame::Reflow(nsPresContext*    
                           ? aReflowState.ComputedHeight()
                           : kidHeight;
 
   // Make a copy of the reflow state and change the computed width and height
   // to reflect the available space for the fixed items
   nsHTMLReflowState reflowState(aReflowState);
   nsPoint offset = AdjustReflowStateForScrollbars(&reflowState);
 
-#ifdef DEBUG
   if (IsAbsoluteContainer()) {
     NS_ASSERTION(GetAbsoluteContainingBlock()->GetChildList().IsEmpty() ||
                  (offset.x == 0 && offset.y == 0),
                  "We don't handle correct positioning of fixed frames with "
                  "scrollbars in odd positions");
-  }
-#endif
 
-  if (IsAbsoluteContainer()) {
     // Just reflow all the fixed-pos frames.
     rv = GetAbsoluteContainingBlock()->Reflow(this, aPresContext, reflowState, aStatus,
                                               reflowState.ComputedWidth(),
                                               reflowState.ComputedHeight(),
                                               false, true, true, // XXX could be optimized
-                                              nsnull /* ignore overflow */);
+                                              &aDesiredSize.mOverflowAreas);
   }
 
   // If we were dirty then do a repaint
   if (GetStateBits() & NS_FRAME_IS_DIRTY) {
     nsRect damageRect(0, 0, aDesiredSize.width, aDesiredSize.height);
     Invalidate(damageRect);
   }
 
-  // XXX Should we do something to clip our children to this?
-  aDesiredSize.SetOverflowAreasToDesiredBounds();
+  // Clipping is handled by the document container (e.g., nsSubDocumentFrame),
+  // so we don't need to change our overflow areas.
+  bool overflowChanged = FinishAndStoreOverflow(&aDesiredSize);
+  if (overflowChanged) {
+    // We may need to alert our container to get it to pick up the
+    // overflow change.
+    nsSubDocumentFrame* container = static_cast<nsSubDocumentFrame*>
+      (nsLayoutUtils::GetCrossDocParentFrame(this));
+    if (container && !container->ShouldClipSubdocument()) {
+      container->PresContext()->PresShell()->
+        FrameNeedsReflow(container, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
+    }
+  }
 
   NS_FRAME_TRACE_REFLOW_OUT("ViewportFrame::Reflow", aStatus);
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
   return rv; 
 }
 
 nsIAtom*
 ViewportFrame::GetType() const