Bug 417255. Rework getClientRects/getBoundingClientRect/offset* code to use a generic rectangle iterator API which drills down through anonymous blocks, fixing IE compat. r+sr=dbaron

This commit is contained in:
roc+%cs.cmu.edu 2008-02-27 09:26:17 +00:00
Родитель 964b6b2778
Коммит 5d3a393ced
7 изменённых файлов: 191 добавлений и 122 удалений

Просмотреть файл

@ -810,19 +810,16 @@ nsNSElementTearoff::GetElementsByClassName(const nsAString& aClasses,
return nsDocument::GetElementsByClassNameHelper(mContent, aClasses, aReturn); return nsDocument::GetElementsByClassNameHelper(mContent, aClasses, aReturn);
} }
static nsPoint static nsIFrame*
GetOffsetFromInitialContainingBlock(nsIFrame* aFrame) GetContainingBlockForClientRect(nsIFrame* aFrame)
{ {
nsIFrame* refFrame = aFrame->GetParent();
if (!refFrame)
return nsPoint(0, 0);
// get the nearest enclosing SVG foreign object frame or the root frame // get the nearest enclosing SVG foreign object frame or the root frame
while (refFrame->GetParent() && while (aFrame->GetParent() &&
!refFrame->IsFrameOfType(nsIFrame::eSVGForeignObject)) !aFrame->IsFrameOfType(nsIFrame::eSVGForeignObject)) {
refFrame = refFrame->GetParent(); aFrame = aFrame->GetParent();
}
return aFrame->GetOffsetTo(refFrame); return aFrame;
} }
static double static double
@ -847,25 +844,6 @@ SetTextRectangle(const nsRect& aLayoutRect, nsPresContext* aPresContext,
RoundFloat(aLayoutRect.YMost()*t2pScaled)*scaleInv - y); RoundFloat(aLayoutRect.YMost()*t2pScaled)*scaleInv - y);
} }
static PRBool
TryGetSVGBoundingRect(nsIFrame* aFrame, nsRect* aRect)
{
#ifdef MOZ_SVG
nsRect r;
nsIFrame* outer = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
if (!outer)
return PR_FALSE;
// r is in pixels relative to 'outer', get it into twips
// relative to ICB origin
r.ScaleRoundOut(1.0/aFrame->PresContext()->AppUnitsPerDevPixel());
*aRect = r + GetOffsetFromInitialContainingBlock(outer);
return PR_TRUE;
#else
return PR_FALSE;
#endif
}
NS_IMETHODIMP NS_IMETHODIMP
nsNSElementTearoff::GetBoundingClientRect(nsIDOMTextRectangle** aResult) nsNSElementTearoff::GetBoundingClientRect(nsIDOMTextRectangle** aResult)
{ {
@ -881,75 +859,44 @@ nsNSElementTearoff::GetBoundingClientRect(nsIDOMTextRectangle** aResult)
// display:none, perhaps? Return the empty rect // display:none, perhaps? Return the empty rect
return NS_OK; return NS_OK;
} }
nsPresContext* presContext = frame->PresContext(); nsPresContext* presContext = frame->PresContext();
nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(frame,
nsRect r; GetContainingBlockForClientRect(frame));
if (TryGetSVGBoundingRect(frame, &r)) {
// Currently SVG frames don't have continuations but I don't want things to
// break if that changes.
while ((frame = nsLayoutUtils::GetNextContinuationOrSpecialSibling(frame)) != nsnull) {
nsRect nextRect;
#ifdef DEBUG
PRBool isSVG =
#endif
TryGetSVGBoundingRect(frame, &nextRect);
NS_ASSERTION(isSVG, "SVG frames must have SVG continuations");
r.UnionRect(r, nextRect);
}
} else {
// The weird frame layout of tables requires this
if (frame->GetType() == nsGkAtoms::tableOuterFrame) {
nsIFrame* innerTable = frame->GetFirstChild(nsnull);
if (innerTable) {
r = nsLayoutUtils::GetAllInFlowBoundingRect(innerTable) + innerTable->GetPosition();
}
nsIFrame* caption = frame->GetFirstChild(nsGkAtoms::captionList);
if (caption) {
r.UnionRect(r, nsLayoutUtils::GetAllInFlowBoundingRect(caption) + caption->GetPosition());
}
} else {
r = nsLayoutUtils::GetAllInFlowBoundingRect(frame);
}
r += GetOffsetFromInitialContainingBlock(frame);
}
SetTextRectangle(r, presContext, rect); SetTextRectangle(r, presContext, rect);
return NS_OK; return NS_OK;
} }
static nsresult struct RectListBuilder : public nsLayoutUtils::RectCallback {
AddRectanglesForFrames(nsTextRectangleList* aRectList, nsIFrame* aFrame) nsPresContext* mPresContext;
{ nsTextRectangleList* mRectList;
if (!aFrame) nsresult mRV;
return NS_OK;
nsPresContext* presContext = aFrame->PresContext(); RectListBuilder(nsPresContext* aPresContext, nsTextRectangleList* aList)
for (nsIFrame* f = aFrame; f; : mPresContext(aPresContext), mRectList(aList),
f = nsLayoutUtils::GetNextContinuationOrSpecialSibling(f)) { mRV(NS_OK) {}
virtual void AddRect(const nsRect& aRect) {
nsRefPtr<nsTextRectangle> rect = new nsTextRectangle(); nsRefPtr<nsTextRectangle> rect = new nsTextRectangle();
if (!rect) if (!rect) {
return NS_ERROR_OUT_OF_MEMORY; mRV = NS_ERROR_OUT_OF_MEMORY;
return;
nsRect r;
if (!TryGetSVGBoundingRect(f, &r)) {
r = nsRect(GetOffsetFromInitialContainingBlock(f), f->GetSize());
} }
SetTextRectangle(r, presContext, rect);
aRectList->Append(rect); SetTextRectangle(aRect, mPresContext, rect);
mRectList->Append(rect);
} }
};
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsNSElementTearoff::GetClientRects(nsIDOMTextRectangleList** aResult) nsNSElementTearoff::GetClientRects(nsIDOMTextRectangleList** aResult)
{ {
*aResult = nsnull; *aResult = nsnull;
// Weak ref, since we addref it below
nsRefPtr<nsTextRectangleList> rectList = new nsTextRectangleList(); nsRefPtr<nsTextRectangleList> rectList = new nsTextRectangleList();
if (!rectList) if (!rectList)
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
nsIFrame* frame = mContent->GetPrimaryFrame(Flush_Layout); nsIFrame* frame = mContent->GetPrimaryFrame(Flush_Layout);
if (!frame) { if (!frame) {
// display:none, perhaps? Return an empty list // display:none, perhaps? Return an empty list
@ -957,21 +904,11 @@ nsNSElementTearoff::GetClientRects(nsIDOMTextRectangleList** aResult)
return NS_OK; return NS_OK;
} }
if (frame->GetType() == nsGkAtoms::tableOuterFrame) { RectListBuilder builder(frame->PresContext(), rectList);
// The weird frame layout of tables requires this nsLayoutUtils::GetAllInFlowRects(frame,
nsIFrame* innerTable = frame->GetFirstChild(nsnull); GetContainingBlockForClientRect(frame), &builder);
nsresult rv = AddRectanglesForFrames(rectList, innerTable); if (NS_FAILED(builder.mRV))
if (NS_FAILED(rv)) return builder.mRV;
return rv;
nsIFrame* caption = frame->GetFirstChild(nsGkAtoms::captionList);
rv = AddRectanglesForFrames(rectList, caption);
if (NS_FAILED(rv))
return rv;
} else {
nsresult rv = AddRectanglesForFrames(rectList, frame);
if (NS_FAILED(rv))
return rv;
}
*aResult = rectList.forget().get(); *aResult = rectList.forget().get();
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -170,6 +170,7 @@ _TEST_FILES = test_bug5141.html \
test_bug414190.html \ test_bug414190.html \
test_bug414796.html \ test_bug414796.html \
test_bug416383.html \ test_bug416383.html \
test_bug417255.html \
test_bug417384.html \ test_bug417384.html \
test_bug418214.html \ test_bug418214.html \
$(NULL) $(NULL)

Просмотреть файл

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=417255
-->
<head>
<title>Test for Bug 417255</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
.spacer { display:inline-block; height:10px; }
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=417255">Mozilla Bug 417255</a>
<p id="display" style="width:800px"></p>
<p><span id="s1" style="border:2px dotted red;"><span class="spacer" style="width:100px"></span>
<div style="width:500px; height:100px; background:yellow;"></div>
<span class="spacer" style="width:200px"></span></span>
<p><span id="s2" style="border:2px dotted red;"><span class="spacer" style="width:100px"></span>
<div style="width:150px; height:100px; background:yellow;"></div>
<span class="spacer" style="width:200px"></span></span>
<!-- test nested spans around the IB split -->
<p><span id="s3" style="border:2px dotted red;"><span><span class="spacer" style="width:100px"></span>
<div style="width:500px; height:100px; background:yellow;"></div>
<span class="spacer" style="width:200px"></span></span></span>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
function getWidth(box) {
return box.right - box.left;
}
function doTest(id, boundsWidth, w1, w2, w3) {
var s = document.getElementById(id);
is(s.offsetWidth, boundsWidth, "bad offsetWidth");
is(getWidth(s.getBoundingClientRect()), boundsWidth, "bad getBoundingClientRect width");
is(getWidth(s.getClientRects()[0]), w1, "bad getClientRects width");
is(getWidth(s.getClientRects()[1]), w2, "bad getClientRects width");
is(getWidth(s.getClientRects()[2]), w3, "bad getClientRects width");
}
doTest("s1", 500, 104, 500, 204);
doTest("s2", 204, 104, 150, 204);
doTest("s3", 500, 104, 500, 204);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -512,8 +512,6 @@ nsGenericHTMLElement::GetOffsetRect(nsRect& aRect, nsIContent** aOffsetParent)
parent = parent->GetParent(); parent = parent->GetParent();
} }
// Get the union of all rectangles in this and continuation frames.
nsRect rcFrame = nsLayoutUtils::GetAllInFlowBoundingRect(frame);
nsIContent* docElement = GetCurrentDoc()->GetRootContent(); nsIContent* docElement = GetCurrentDoc()->GetRootContent();
nsIContent* content = frame->GetContent(); nsIContent* content = frame->GetContent();
@ -594,6 +592,12 @@ nsGenericHTMLElement::GetOffsetRect(nsRect& aRect, nsIContent** aOffsetParent)
// Convert to pixels. // Convert to pixels.
aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x); aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y); aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
// Get the union of all rectangles in this and continuation frames.
// It doesn't really matter what we use as aRelativeTo here, since
// we only care about the size. Using 'parent' might make things
// a bit faster by speeding up the internal GetOffsetTo operations.
nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, parent);
aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width); aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width);
aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height); aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height);
} }

Просмотреть файл

@ -47,6 +47,7 @@
#include "nsGkAtoms.h" #include "nsGkAtoms.h"
#include "nsIAtom.h" #include "nsIAtom.h"
#include "nsCSSPseudoElements.h" #include "nsCSSPseudoElements.h"
#include "nsCSSAnonBoxes.h"
#include "nsIView.h" #include "nsIView.h"
#include "nsIScrollableView.h" #include "nsIScrollableView.h"
#include "nsPlaceholderFrame.h" #include "nsPlaceholderFrame.h"
@ -70,9 +71,11 @@
#include "nsCSSRendering.h" #include "nsCSSRendering.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#ifdef MOZ_SVG
#include "nsSVGUtils.h"
#endif
#ifdef MOZ_SVG_FOREIGNOBJECT #ifdef MOZ_SVG_FOREIGNOBJECT
#include "nsSVGForeignObjectFrame.h" #include "nsSVGForeignObjectFrame.h"
#include "nsSVGUtils.h"
#include "nsSVGOuterSVGFrame.h" #include "nsSVGOuterSVGFrame.h"
#endif #endif
@ -1080,27 +1083,73 @@ nsLayoutUtils::BinarySearchForPosition(nsIRenderingContext* aRendContext,
return PR_FALSE; return PR_FALSE;
} }
nsRect static void
nsLayoutUtils::GetAllInFlowBoundingRect(nsIFrame* aFrame) AddRectsForFrame(nsIFrame* aFrame, nsIFrame* aRelativeTo,
nsLayoutUtils::RectCallback* aCallback)
{ {
// Get the union of all rectangles in this and continuation frames nsIAtom* pseudoType = aFrame->GetStyleContext()->GetPseudoType();
nsRect r = aFrame->GetRect();
nsIFrame* parent = aFrame->GetParent();
if (!parent)
return r;
for (nsIFrame* f = nsLayoutUtils::GetNextContinuationOrSpecialSibling(aFrame); if (pseudoType == nsCSSAnonBoxes::tableOuter) {
f; f = nsLayoutUtils::GetNextContinuationOrSpecialSibling(f)) { AddRectsForFrame(aFrame->GetFirstChild(nsnull), aRelativeTo,
r.UnionRect(r, nsRect(f->GetOffsetTo(parent), f->GetSize())); aCallback);
nsIFrame* kid = aFrame->GetFirstChild(nsGkAtoms::captionList);
if (kid) {
AddRectsForFrame(kid, aRelativeTo, aCallback);
}
} else if (pseudoType == nsCSSAnonBoxes::mozAnonymousBlock ||
pseudoType == nsCSSAnonBoxes::mozAnonymousPositionedBlock ||
pseudoType == nsCSSAnonBoxes::mozMathMLAnonymousBlock ||
pseudoType == nsCSSAnonBoxes::mozXULAnonymousBlock) {
for (nsIFrame* kid = aFrame->GetFirstChild(nsnull); kid; kid = kid->GetNextSibling()) {
AddRectsForFrame(kid, aRelativeTo, aCallback);
}
} else {
#ifdef MOZ_SVG
nsRect r;
nsIFrame* outer = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
if (outer) {
// r is in pixels relative to 'outer', get it into appunits
// relative to aRelativeTo
r.ScaleRoundOut(1.0/aFrame->PresContext()->AppUnitsPerDevPixel());
aCallback->AddRect(r + outer->GetOffsetTo(aRelativeTo));
} else
#endif
aCallback->AddRect(nsRect(aFrame->GetOffsetTo(aRelativeTo), aFrame->GetSize()));
} }
}
if (r.IsEmpty()) { void
// It could happen that all the rects are empty (eg zero-width or nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
// zero-height). In that case, use the first rect for the frame. RectCallback* aCallback)
r = aFrame->GetRect(); {
while (aFrame) {
AddRectsForFrame(aFrame, aRelativeTo, aCallback);
aFrame = nsLayoutUtils::GetNextContinuationOrSpecialSibling(aFrame);
} }
}
return r - aFrame->GetPosition(); 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;
}
}
};
nsRect
nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo) {
RectAccumulator accumulator;
GetAllInFlowRects(aFrame, aRelativeTo, &accumulator);
return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
: accumulator.mResultRect;
} }
nsresult nsresult

Просмотреть файл

@ -471,13 +471,27 @@ public:
PRInt32& aIndex, PRInt32& aIndex,
PRInt32& aTextWidth); PRInt32& aTextWidth);
class RectCallback {
public:
virtual void AddRect(const nsRect& aRect) = 0;
};
/** /**
* Get the union of all rects in aFrame and its continuations, relative * Collect all CSS border-boxes associated with aFrame and its
* to aFrame's origin. Scrolling is taken into account, but this shouldn't * continuations, "drilling down" through outer table frames and
* matter because it should be impossible to have some continuations scrolled * some anonymous blocks since they're not real CSS boxes.
* differently from others. * 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.
*/ */
static nsRect GetAllInFlowBoundingRect(nsIFrame* aFrame); static void GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
RectCallback* aCallback);
/**
* Computes the union of all rects returned by GetAllInFlowRects. If
* the union is empty, returns the first rect.
*/
static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo);
/** /**
* Get the font metrics corresponding to the frame's style data. * Get the font metrics corresponding to the frame's style data.

Просмотреть файл

@ -173,9 +173,6 @@ nsBoxObject::GetOffsetRect(nsRect& aRect)
// Get its origin // Get its origin
nsPoint origin = frame->GetPositionIgnoringScrolling(); nsPoint origin = frame->GetPositionIgnoringScrolling();
// Get the union of all rectangles in this and continuation frames
nsRect rcFrame = nsLayoutUtils::GetAllInFlowBoundingRect(frame);
// Find the frame parent whose content is the document element. // Find the frame parent whose content is the document element.
nsIContent *docElement = mContent->GetCurrentDoc()->GetRootContent(); nsIContent *docElement = mContent->GetCurrentDoc()->GetRootContent();
nsIFrame* parent = frame->GetParent(); nsIFrame* parent = frame->GetParent();
@ -210,10 +207,16 @@ nsBoxObject::GetOffsetRect(nsRect& aRect)
aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x); aRect.x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y); aRect.y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
// Get the union of all rectangles in this and continuation frames.
// It doesn't really matter what we use as aRelativeTo here, since
// we only care about the size. Using 'parent' might make things
// a bit faster by speeding up the internal GetOffsetTo operations.
nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, parent);
aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width); aRect.width = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.width);
aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height); aRect.height = nsPresContext::AppUnitsToIntCSSPixels(rcFrame.height);
} }
return NS_OK; return NS_OK;
} }