Bug 396392 - Support for getClientRects and getBoundingClientRect in DOM Range
authorliucougar <liucougar@gmail.com>
Thu, 24 Sep 2009 23:01:48 +0200
changeset 33027 3262c0bd49ac05807cba2bcc9dece36afbff21f0
parent 33026 b0c89755c903ca073c9cf6044958bf065cb197b4
child 33028 c3541686e7cfbb6397a7b655e079baa158aa8649
push id9307
push usersgautherie.bz@free.fr
push dateThu, 24 Sep 2009 21:02:10 +0000
treeherdermozilla-central@3262c0bd49ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs396392
milestone1.9.3a1pre
Bug 396392 - Support for getClientRects and getBoundingClientRect in DOM Range r=roc sr=(jonas, roc)
content/base/src/Makefile.in
content/base/src/nsGenericElement.cpp
content/base/src/nsRange.cpp
content/base/test/Makefile.in
content/base/test/test_range_bounds.html
dom/interfaces/range/nsIDOMRange.idl
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -141,17 +141,17 @@ FORCE_STATIC_LIB = 1
 
 EXTRA_COMPONENTS = $(srcdir)/nsBadCertHandler.js
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../events/src \
 		-I$(srcdir)/../../xml/content/src \
-		-I$(srcdir)/../../xul/base/src \
+		-I$(srcdir)/../../../layout/xul/base/src \
 		-I$(srcdir)/../../xul/content/src \
 		-I$(srcdir)/../../html/content/src \
 		-I$(srcdir)/../../base/src \
 		-I$(srcdir)/../../xbl/src \
 		-I$(srcdir)/../../../layout/generic \
 		-I$(srcdir)/../../../layout/style \
 		-I$(srcdir)/../../../dom/base \
 		-I$(srcdir)/../../xml/document/src \
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -1429,28 +1429,16 @@ nsNSElementTearoff::GetClientHeight(PRIn
 nsresult
 nsNSElementTearoff::GetClientWidth(PRInt32* aLength)
 {
   NS_ENSURE_ARG_POINTER(aLength);
   *aLength = nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().width);
   return NS_OK;
 }
 
-static nsIFrame*
-GetContainingBlockForClientRect(nsIFrame* aFrame)
-{
-  // get the nearest enclosing SVG foreign object frame or the root frame
-  while (aFrame->GetParent() &&
-         !aFrame->IsFrameOfType(nsIFrame::eSVGForeignObject)) {
-    aFrame = aFrame->GetParent();
-  }
-
-  return aFrame;
-}
-
 NS_IMETHODIMP
 nsNSElementTearoff::GetBoundingClientRect(nsIDOMClientRect** aResult)
 {
   // Weak ref, since we addref it below
   nsClientRect* rect = new nsClientRect();
   if (!rect)
     return NS_ERROR_OUT_OF_MEMORY;
 
@@ -1458,59 +1446,40 @@ nsNSElementTearoff::GetBoundingClientRec
   
   nsIFrame* frame = mContent->GetPrimaryFrame(Flush_Layout);
   if (!frame) {
     // display:none, perhaps? Return the empty rect
     return NS_OK;
   }
 
   nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(frame,
-          GetContainingBlockForClientRect(frame));
+          nsLayoutUtils::GetContainingBlockForClientRect(frame));
   rect->SetLayoutRect(r);
   return NS_OK;
 }
 
-struct RectListBuilder : public nsLayoutUtils::RectCallback {
-  nsClientRectList* mRectList;
-  nsresult          mRV;
-
-  RectListBuilder(nsClientRectList* aList) 
-    : mRectList(aList), mRV(NS_OK) {}
-
-  virtual void AddRect(const nsRect& aRect) {
-    nsRefPtr<nsClientRect> rect = new nsClientRect();
-    if (!rect) {
-      mRV = NS_ERROR_OUT_OF_MEMORY;
-      return;
-    }
-    
-    rect->SetLayoutRect(aRect);
-    mRectList->Append(rect);
-  }
-};
-
 NS_IMETHODIMP
 nsNSElementTearoff::GetClientRects(nsIDOMClientRectList** aResult)
 {
   *aResult = nsnull;
 
   nsRefPtr<nsClientRectList> rectList = new nsClientRectList();
   if (!rectList)
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsIFrame* frame = mContent->GetPrimaryFrame(Flush_Layout);
   if (!frame) {
     // display:none, perhaps? Return an empty list
     *aResult = rectList.forget().get();
     return NS_OK;
   }
 
-  RectListBuilder builder(rectList);
+  nsLayoutUtils::RectListBuilder builder(rectList);
   nsLayoutUtils::GetAllInFlowRects(frame,
-          GetContainingBlockForClientRect(frame), &builder);
+          nsLayoutUtils::GetContainingBlockForClientRect(frame), &builder);
   if (NS_FAILED(builder.mRV))
     return builder.mRV;
   *aResult = rectList.forget().get();
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -52,16 +52,19 @@
 #include "nsIDocument.h"
 #include "nsIDOMText.h"
 #include "nsDOMError.h"
 #include "nsIContentIterator.h"
 #include "nsIDOMNodeList.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 #include "nsGenericDOMDataNode.h"
+#include "nsClientRect.h"
+#include "nsLayoutUtils.h"
+#include "nsTextFrame.h"
 
 nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult);
 nsresult NS_NewContentSubtreeIterator(nsIContentIterator** aInstancePtrResult);
 
 /******************************************************
  * stack based utilty class for managing monitor
  ******************************************************/
 
@@ -2030,8 +2033,193 @@ nsRange::CreateContextualFragment(const 
 {
   nsCOMPtr<nsIDOMNode> start = do_QueryInterface(mStartParent);
   if (mIsPositioned) {
     return nsContentUtils::CreateContextualFragment(start, aFragment, PR_TRUE,
                                                     aReturn);
   }
   return NS_ERROR_FAILURE;
 }
+
+static void ExtractRectFromOffset(nsIFrame* aFrame,
+                                  const nsIFrame* aRelativeTo, 
+                                  const PRInt32 aOffset, nsRect* aR, PRBool aKeepLeft)
+{
+  nsPoint point;
+  aFrame->GetPointFromOffset(aOffset, &point);
+
+  point += aFrame->GetOffsetTo(aRelativeTo);
+
+  //given a point.x, extract left or right portion of rect aR
+  //point.x has to be within this rect
+  NS_ASSERTION(aR->x <= point.x && point.x <= aR->XMost(),
+                   "point.x should not be outside of rect r");
+
+  if (aKeepLeft) {
+    aR->width = point.x - aR->x;
+  } else {
+    aR->width = aR->XMost() - point.x;
+    aR->x = point.x;
+  }
+}
+
+static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback, nsIPresShell* aPresShell, 
+                                   nsIContent* aContent, PRInt32 aStartOffset, PRInt32 aEndOffset)
+{
+  nsIFrame* frame = aPresShell->GetPrimaryFrameFor(aContent);
+  if (frame && frame->GetType() == nsGkAtoms::textFrame) {
+    nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+    nsIFrame* relativeTo = nsLayoutUtils::GetContainingBlockForClientRect(textFrame);
+    for (nsTextFrame* f = textFrame; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
+      PRInt32 fstart = f->GetContentOffset(), fend = f->GetContentEnd();
+      PRBool rtl = f->GetTextRun()->IsRightToLeft();
+      if (fend <= aStartOffset || fstart >= aEndOffset)
+        continue;
+
+      //overlaping with the offset we want
+      nsRect r(f->GetOffsetTo(relativeTo), f->GetSize());
+      if (fstart < aStartOffset) {
+        //aStartOffset is within this frame
+        ExtractRectFromOffset(f, relativeTo, aStartOffset, &r, rtl);
+      }
+      if (fend > aEndOffset) {
+        //aEndOffset is in the middle of this frame
+        ExtractRectFromOffset(f, relativeTo, aEndOffset, &r, !rtl);
+      }
+      aCallback->AddRect(r);
+    }
+  }
+  return NS_OK;
+}
+
+static nsIPresShell* GetPresShell(nsINode* aNode, PRBool aFlush)
+{
+  nsCOMPtr<nsIDocument> document = aNode->GetCurrentDoc();
+  if (!document)
+    return nsnull;
+
+  if (aFlush) {
+    document->FlushPendingNotifications(Flush_Layout);
+  }
+
+  return document->GetPrimaryShell();
+}
+
+static void CollectClientRects(nsLayoutUtils::RectCallback* aCollector, 
+                               nsRange* aRange,
+                               nsINode* aStartParent, PRInt32 aStartOffset,
+                               nsINode* aEndParent, PRInt32 aEndOffset)
+{
+  nsCOMPtr<nsIDOMNode> startContainer = do_QueryInterface(aStartParent);
+  nsCOMPtr<nsIDOMNode> endContainer = do_QueryInterface(aEndParent);
+
+  nsIPresShell* presShell = GetPresShell(aStartParent, PR_TRUE);
+  if (!presShell) {
+    //not in the document
+    return;
+  }
+
+  RangeSubtreeIterator iter;
+
+  nsresult rv = iter.Init(aRange);
+  if (NS_FAILED(rv)) return;
+
+  if (iter.IsDone()) {
+    // the range is collapsed, only continue if the cursor is in a text node
+    nsCOMPtr<nsIContent> content = do_QueryInterface(aStartParent);
+    if (content->IsNodeOfType(nsINode::eTEXT)) {
+      nsIFrame* frame = presShell->GetPrimaryFrameFor(content);
+      if (frame && frame->GetType() == nsGkAtoms::textFrame) {
+        nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+        PRInt32 outOffset;
+        nsIFrame* outFrame;
+        textFrame->GetChildFrameContainingOffset(aStartOffset, PR_FALSE, 
+          &outOffset, &outFrame);
+        if (outFrame) {
+           nsIFrame* relativeTo = 
+             nsLayoutUtils::GetContainingBlockForClientRect(outFrame);
+           nsRect r(outFrame->GetOffsetTo(relativeTo), outFrame->GetSize());
+           ExtractRectFromOffset(outFrame, relativeTo, aStartOffset, &r, PR_FALSE);
+           r.width = 0;
+           aCollector->AddRect(r);
+        }
+      }
+    }
+    return;
+  }
+
+  do {
+    nsCOMPtr<nsIDOMNode> node(iter.GetCurrentNode());
+    iter.Next();
+    nsCOMPtr<nsIContent> content = do_QueryInterface(node);
+    if (content->IsNodeOfType(nsINode::eTEXT)) {
+       if (node == startContainer) {
+         PRInt32 offset = startContainer == endContainer ? 
+           aEndOffset : content->GetText()->GetLength();
+         GetPartialTextRect(aCollector, presShell, content, 
+           aStartOffset, offset);
+         continue;
+       } else if (node == endContainer) {
+         GetPartialTextRect(aCollector, presShell, content, 
+           0, aEndOffset);
+         continue;	 
+       }
+    }
+
+    nsIFrame* frame = presShell->GetPrimaryFrameFor(content);
+    if (frame) {
+      nsLayoutUtils::GetAllInFlowRects(frame,
+        nsLayoutUtils::GetContainingBlockForClientRect(frame), aCollector);
+    }
+  } while (!iter.IsDone());
+}
+
+NS_IMETHODIMP
+nsRange::GetBoundingClientRect(nsIDOMClientRect** aResult)
+{
+  // Weak ref, since we addref it below
+  nsClientRect* rect = new nsClientRect();
+  if (!rect)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  NS_ADDREF(*aResult = rect);
+
+  nsIPresShell* presShell = GetPresShell(mStartParent, PR_FALSE);
+  if (!presShell)
+    return NS_OK;
+
+  nsLayoutUtils::RectAccumulator accumulator;
+  
+  CollectClientRects(&accumulator, this, mStartParent, mStartOffset, 
+    mEndParent, mEndOffset);
+
+  nsRect r = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect : 
+    accumulator.mResultRect;
+  rect->SetLayoutRect(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRange::GetClientRects(nsIDOMClientRectList** aResult)
+{
+  *aResult = nsnull;
+
+  nsRefPtr<nsClientRectList> rectList = new nsClientRectList();
+  if (!rectList)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  nsIPresShell* presShell = GetPresShell(mStartParent, PR_FALSE);
+  if (!presShell) {
+    rectList.forget(aResult);
+    return NS_OK;
+  }
+
+  nsLayoutUtils::RectListBuilder builder(rectList);
+
+  CollectClientRects(&builder, this, mStartParent, mStartOffset, 
+    mEndParent, mEndOffset);
+
+  if (NS_FAILED(builder.mRV))
+    return builder.mRV;
+  rectList.forget(aResult);
+  return NS_OK;
+}
+
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -306,16 +306,17 @@ include $(topsrcdir)/config/rules.mk
 		file_bug498897.css \
 		test_bug493881.js \
 		test_bug493881.html \
 		bug466409-page.html \
 		bug466409-empty.css \
 		test_bug466409.html \
 		test_classList.html \
 		test_bug489925.xhtml \
+		test_range_bounds.html \
 		test_bug514487.html \
 		$(NULL)
 # Disabled; see bug 492181
 #		test_plugin_freezing.html
 
 # Disabled for now. Mochitest isn't reliable enough for these.
 # test_bug444546.html \
 # bug444546.sjs \
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_range_bounds.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=421640
+-->
+<head>
+  <title>Test for Bug 396392</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396392">Mozilla Bug Range getClientRects and getBoundingClientRect</a>
+<div id="content" style="font-face:monospace;font-size:12px;width:100px">
+<p>000000</p><div>00000</div><p>0000<span>0000</span>0000</p>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function isEmptyRect(rect, name) {
+  is(rect.left, 0, name+'empty rect should have left = 0');
+  is(rect.right, 0, name+'empty rect should have right = 0');
+  is(rect.top, 0, name+'empty rect should have top = 0');
+  is(rect.bottom, 0, name+'empty rect should have bottom = 0');
+  is(rect.width, 0, name+'empty rect should have width = 0');
+  is(rect.height, 0, name+'empty rect should have height = 0');
+}
+
+function isEmptyRectList(rectlist, name) {
+  is(rectlist.length, 0, name + 'empty rectlist should have zero rects');
+}
+
+function _getRect(r) {
+  if (r.length) //array
+    return "{left:"+r[0]+",right:"+r[1]+",top:"+r[2]+",bottom:"+r[3]
+      +",width:"+r[4]+",height:"+r[5]+"}";
+  else
+    return "{left:"+r.left+",right:"+r.right+",top:"+r.top+",bottom:"
+      +r.bottom+",width:"+r.width+",height:"+r.height+"}";
+}
+
+function runATest(obj) {
+  var range = document.createRange();
+  try {
+    range.setStart(obj.range[0],obj.range[1]);
+    if (obj.range.length>2) {
+       range.setEnd(obj.range[2]||obj.range[0], obj.range[3]);
+    }
+    //test getBoundingClientRect()
+    var rect = range.getBoundingClientRect();
+    var testname = 'range.getBoundingClientRect for ' + obj.name;
+    if (obj.rect) {
+      is(_getRect(rect),_getRect(obj.rect), testname);
+    } else {
+      isEmptyRect(rect,testname+": ");
+    }
+    //test getClientRects()
+    var rectlist = range.getClientRects();
+    testname = 'range.getClientRects for '+obj.name;
+    if (!obj.rectList) {
+      //rectList is not specified, use obj.rect to figure out rectList
+      obj.rectList = obj.rect?[obj.rect]:[];
+    }
+    if (!obj.rectList.length) {
+      isEmptyRectList(rectlist, testname+": ");
+    } else {
+      is(rectlist.length, obj.rectList.length, testname+' should return '+obj.rectList.length+' rects.');
+      obj.rectList.forEach(function(rect,i) {
+        is(_getRect(rectlist[i]),_getRect(rect),testname+": item at "+i);
+      });
+    }
+  } finally {
+    range.detach();
+  }
+}
+/** Test for Bug 396392 **/
+function test() {
+  var root = document.getElementById('content');
+  var firstP = root.firstElementChild, firstDiv = root.childNodes[2], 
+    secondP = root.childNodes[3], spanInSecondP = secondP.childNodes[1];
+  var firstPRect = firstP.getBoundingClientRect(), 
+    firstDivRect = firstDiv.getBoundingClientRect(),
+    secondPRect = secondP.getBoundingClientRect(),
+    spanInSecondPRect = spanInSecondP.getBoundingClientRect();
+  var widthPerchar = spanInSecondPRect.width / spanInSecondP.firstChild.length;
+  var testcases = [
+    {name:'nodesNotInDocument', range:[document.createTextNode('abc'), 1], 
+      rect:null},
+    {name:'collapsedInBlockNode', range:[firstP, 1], rect:null},
+    {name:'collapsedAtBeginningOfTextNode', range:[firstP.firstChild, 0],
+      rect:[firstPRect.left, firstPRect.left, firstPRect.top, 
+      firstPRect.bottom, 0, firstPRect.height]},
+    {name:'collapsedWithinTextNode', range:[firstP.firstChild, 1], 
+      rect:[firstPRect.left+widthPerchar, firstPRect.left+widthPerchar,
+        firstPRect.top, firstPRect.bottom, 0, firstPRect.height]},
+    {name:'collapsedAtEndOfTextNode', range:[firstP.firstChild, 6], 
+      rect:[firstPRect.left + 6*widthPerchar, firstPRect.left + 6*widthPerchar,
+        firstPRect.top, firstPRect.bottom, 0, firstPRect.height]},
+    {name:'singleBlockNode', range:[root, 1, root, 2], rect:firstPRect},
+    {name:'twoBlockNodes', range:[root, 1, root, 3],
+      rect:[firstPRect.left, firstPRect.right, firstPRect.top,
+        firstDivRect.bottom, firstPRect.width,
+        firstDivRect.bottom - firstPRect.top],
+      rectList:[firstPRect, firstDivRect]},
+    {name:'endOfTextNodeToEndOfAnotherTextNodeInAnotherBlock',
+      range:[firstP.firstChild, 6, firstDiv.firstChild, 5],
+      rect:[firstDivRect.left, firstDivRect.left + 5*widthPerchar,
+        firstDivRect.top, firstDivRect.bottom, 5 * widthPerchar, 
+        firstDivRect.height]},
+    {name:'startOfTextNodeToStartOfAnotherTextNodeInAnotherBlock', 
+      range:[firstP.firstChild, 0, firstDiv.firstChild, 0],
+      rect:[firstPRect.left, firstPRect.left + 6*widthPerchar, firstPRect.top,
+        firstPRect.bottom, 6 * widthPerchar, firstPRect.height]},
+    {name:'endPortionOfATextNode', range:[firstP.firstChild, 3, 
+        firstP.firstChild, 6],
+      rect:[firstPRect.left + 3*widthPerchar, firstPRect.left + 6*widthPerchar,
+        firstPRect.top, firstPRect.bottom, 3*widthPerchar, firstPRect.height]},
+    {name:'startPortionOfATextNode', range:[firstP.firstChild, 0, 
+        firstP.firstChild, 3],
+      rect:[firstPRect.left, firstPRect.left + 3*widthPerchar, firstPRect.top,
+        firstPRect.bottom, 3 * widthPerchar, firstPRect.height]},
+    {name:'spanTextNodes', range:[secondP.firstChild, 1, secondP.lastChild, 1],
+      rect:[secondPRect.left + widthPerchar, spanInSecondPRect.right + 
+        widthPerchar, spanInSecondPRect.top, spanInSecondPRect.bottom,
+        spanInSecondPRect.width + 4*widthPerchar, secondPRect.height],
+      rectList:[[secondPRect.left + widthPerchar, spanInSecondPRect.left,
+        spanInSecondPRect.top, spanInSecondPRect.bottom, 3 * widthPerchar,
+        spanInSecondPRect.height],
+	spanInSecondPRect,
+	[spanInSecondPRect.right, spanInSecondPRect.right + widthPerchar,
+          spanInSecondPRect.top, spanInSecondPRect.bottom, widthPerchar,
+          spanInSecondPRect.height]]}
+  ];
+  testcases.forEach(function(tc) {
+    runATest(tc);
+  });
+  //alert(_getRect(rect));
+
+  SimpleTest.finish();
+}
+
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+  setTimeout(test, 0);
+};
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/range/nsIDOMRange.idl
+++ b/dom/interfaces/range/nsIDOMRange.idl
@@ -43,17 +43,17 @@
  * The nsIDOMRange interface is an interface to a DOM range object.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/
  *
  * @status FROZEN
  */
 
-[scriptable, uuid(a6cf90ce-15b3-11d2-932e-00805f8add32)]
+[scriptable, uuid(fed93d11-f24d-41d8-ae55-4197927999bb)]
 interface nsIDOMRange : nsISupports
 {
   readonly attribute nsIDOMNode       startContainer;
                                         // raises(DOMException) on retrieval
 
   readonly attribute long             startOffset;
                                         // raises(DOMException) on retrieval
 
@@ -108,9 +108,12 @@ interface nsIDOMRange : nsISupports
   void               surroundContents(in nsIDOMNode newParent)
                                         raises(DOMException, RangeException);
   nsIDOMRange        cloneRange()
                                         raises(DOMException);
   DOMString          toString()
                                         raises(DOMException);
   void               detach()
                                         raises(DOMException);
+
+  nsIDOMClientRectList getClientRects();
+  nsIDOMClientRect getBoundingClientRect();
 };
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -77,16 +77,17 @@
 #include "gfxMatrix.h"
 #include "gfxTypes.h"
 #include "gfxUserFontSet.h"
 #include "nsTArray.h"
 #include "nsTextFragment.h"
 #include "nsICanvasElement.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "gfxPlatform.h"
+#include "nsClientRect.h"
 #ifdef MOZ_MEDIA
 #include "nsHTMLVideoElement.h"
 #endif
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 #include "nsCOMPtr.h"
 
@@ -1444,31 +1445,50 @@ struct BoxToBorderRect : public nsLayout
 void
 nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                  RectCallback* aCallback)
 {
   BoxToBorderRect converter(aRelativeTo, aCallback);
   GetAllInFlowBoxes(aFrame, &converter);
 }
 
-struct RectAccumulator : public nsLayoutUtils::RectCallback {
-  nsRect       mResultRect;
-  nsRect       mFirstRect;
-  PRPackedBool mSeenFirstRect;
-
-  RectAccumulator() : mSeenFirstRect(PR_FALSE) {}
-
-  virtual void AddRect(const nsRect& aRect) {
-    mResultRect.UnionRect(mResultRect, aRect);
-    if (!mSeenFirstRect) {
-      mSeenFirstRect = PR_TRUE;
-      mFirstRect = aRect;
-    }
+nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(PR_FALSE) {}
+
+void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
+  mResultRect.UnionRect(mResultRect, aRect);
+  if (!mSeenFirstRect) {
+    mSeenFirstRect = PR_TRUE;
+    mFirstRect = aRect;
   }
-};
+}
+
+nsLayoutUtils::RectListBuilder::RectListBuilder(nsClientRectList* aList)
+  : mRectList(aList), mRV(NS_OK) {}
+
+void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) {
+  nsRefPtr<nsClientRect> rect = new nsClientRect();
+  if (!rect) {
+    mRV = NS_ERROR_OUT_OF_MEMORY;
+    return;
+  }
+
+  rect->SetLayoutRect(aRect);
+  mRectList->Append(rect);
+}
+
+nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame)
+{
+  // get the nearest enclosing SVG foreign object frame or the root frame
+  while (aFrame->GetParent() &&
+         !aFrame->IsFrameOfType(nsIFrame::eSVGForeignObject)) {
+    aFrame = aFrame->GetParent();
+  }
+
+  return aFrame;
+}
 
 nsRect
 nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo) {
   RectAccumulator accumulator;
   GetAllInFlowRects(aFrame, aRelativeTo, &accumulator);
   return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
           : accumulator.mResultRect;
 }
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -46,16 +46,17 @@ class nsPresContext;
 class nsIContent;
 class nsIAtom;
 class nsIScrollableView;
 class nsIScrollableFrame;
 class nsIDOMEvent;
 class nsRegion;
 class nsDisplayListBuilder;
 class nsIFontMetrics;
+class nsClientRectList;
 
 #include "prtypes.h"
 #include "nsStyleContext.h"
 #include "nsAutoPtr.h"
 #include "nsStyleSet.h"
 #include "nsIView.h"
 #include "nsIFrame.h"
 #include "nsThreadUtils.h"
@@ -594,16 +595,37 @@ public:
    * SVG frames return a single box, themselves.
    */
   static void GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback);
 
   class RectCallback {
   public:
     virtual void AddRect(const nsRect& aRect) = 0;
   };
+
+  struct RectAccumulator : public RectCallback {
+    nsRect       mResultRect;
+    nsRect       mFirstRect;
+    PRPackedBool mSeenFirstRect;
+
+    RectAccumulator();
+
+    virtual void AddRect(const nsRect& aRect);
+  };
+
+  struct RectListBuilder : public RectCallback {
+    nsClientRectList* mRectList;
+    nsresult          mRV;
+
+    RectListBuilder(nsClientRectList* aList);
+     virtual void AddRect(const nsRect& aRect);
+  };
+
+  static nsIFrame* GetContainingBlockForClientRect(nsIFrame* aFrame);
+
   /**
    * Collect all CSS border-boxes associated with aFrame and its
    * continuations, "drilling down" through outer table frames and
    * some anonymous blocks since they're not real CSS boxes.
    * The boxes are positioned relative to aRelativeTo (taking scrolling
    * into account) and passed to the callback in frame-tree order.
    * If aFrame is null, no boxes are returned.
    * For SVG frames, returns one rectangle, the bounding box.