Bug 1767256 - Include elements with display:contents when calculating range bounding rect r=emilio

Elements with display:contents does not participate in layout, but it's children does.  When
calculating the bounding rect of a range, those children have to be taken into account.

Differential Revision: https://phabricator.services.mozilla.com/D151229
This commit is contained in:
Tiaan Louw 2022-07-08 10:49:47 +00:00
Родитель bc86581122
Коммит 2dff9781d9
2 изменённых файлов: 89 добавлений и 26 удалений

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

@ -27,11 +27,12 @@
#include "mozilla/CheckedInt.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/dom/CharacterData.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/DOMRect.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DocumentType.h"
#include "mozilla/dom/RangeBinding.h"
#include "mozilla/dom/DOMRect.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Text.h"
#include "mozilla/Maybe.h"
@ -2695,6 +2696,50 @@ static nsresult GetPartialTextRect(RectCallback* aCallback,
return NS_OK;
}
static void CollectClientRectsForSubtree(
nsINode* aNode, RectCallback* aCollector, Sequence<nsString>* aTextList,
nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout) {
auto* content = nsIContent::FromNode(aNode);
if (!content) {
return;
}
if (content->IsText()) {
if (aNode == aStartContainer) {
int32_t offset = aStartContainer == aEndContainer
? static_cast<int32_t>(aEndOffset)
: content->AsText()->TextDataLength();
GetPartialTextRect(aCollector, aTextList, content,
static_cast<int32_t>(aStartOffset), offset,
aClampToEdge, aFlushLayout);
return;
}
if (aNode == aEndContainer) {
GetPartialTextRect(aCollector, aTextList, content, 0,
static_cast<int32_t>(aEndOffset), aClampToEdge,
aFlushLayout);
return;
}
}
if (aNode->IsElement() && aNode->AsElement()->IsDisplayContents()) {
FlattenedChildIterator childIter(content);
for (nsIContent* child = childIter.GetNextChild(); child;
child = childIter.GetNextChild()) {
CollectClientRectsForSubtree(child, aCollector, aTextList,
aStartContainer, aStartOffset, aEndContainer,
aEndOffset, aClampToEdge, aFlushLayout);
}
} else if (nsIFrame* frame = content->GetPrimaryFrame()) {
nsLayoutUtils::GetAllInFlowRectsAndTexts(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
}
}
/* static */
void nsRange::CollectClientRectsAndText(
RectCallback* aCollector, Sequence<nsString>* aTextList, nsRange* aRange,
@ -2756,31 +2801,10 @@ void nsRange::CollectClientRectsAndText(
do {
nsCOMPtr<nsINode> node = iter.GetCurrentNode();
iter.Next();
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
if (!content) continue;
if (content->IsText()) {
if (node == startContainer) {
int32_t offset = startContainer == endContainer
? static_cast<int32_t>(aEndOffset)
: content->AsText()->TextDataLength();
GetPartialTextRect(aCollector, aTextList, content,
static_cast<int32_t>(aStartOffset), offset,
aClampToEdge, aFlushLayout);
continue;
} else if (node == endContainer) {
GetPartialTextRect(aCollector, aTextList, content, 0,
static_cast<int32_t>(aEndOffset), aClampToEdge,
aFlushLayout);
continue;
}
}
nsIFrame* frame = content->GetPrimaryFrame();
if (frame) {
nsLayoutUtils::GetAllInFlowRectsAndTexts(
frame, nsLayoutUtils::GetContainingBlockForClientRect(frame),
aCollector, aTextList, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
}
CollectClientRectsForSubtree(node, aCollector, aTextList, aStartContainer,
aStartOffset, aEndContainer, aEndOffset,
aClampToEdge, aFlushLayout);
} while (!iter.IsDone());
}

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

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Include display:contents elements recursively when calculating bounding rect for a ranges</title>
<link rel="help" href="https://drafts.csswg.org/cssom-view-1/#dom-range-getboundingclientrect">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="container">
<div id="spacerBefore">spacer before</div>
<div style="display:contents">
<div style="height:30px; background:lightblue">
HEIGHT: 30px
</div>
<div style="display:contents">
<div style="display:contents">
<div style="height:30px; background:lightblue">
HEIGHT: 30px
</div>
</div>
</div>
</div>
<div id="spacerAfter">spacer after</div>
</div>
<script>
test(function () {
const spacerBefore = document.getElementById("spacerBefore");
const spacerAfter = document.getElementById("spacerAfter");
const expected = spacerAfter.getBoundingClientRect().top - spacerBefore.getBoundingClientRect().bottom;
const rangeBetweenSpacers = document.createRange();
rangeBetweenSpacers.setStartAfter(spacerBefore);
rangeBetweenSpacers.setEndBefore(spacerAfter);
const actual = rangeBetweenSpacers.getBoundingClientRect().height;
assert_true(actual > 0, "range has vertical height");
assert_equals(expected, actual, "range.getBoundingClientRect().height");
}, "the space between elements using a range should be the same as using another method")
</script>