Bug 1739079 - Make ContentIterator more resilient to unexpected situations. r=saschanaz

This shouldn't change behavior in non-hanging cases and should fix the
hang. We had similar hackish:

  mIsDone = mCurNode == nullptr;

For anon content.

Differential Revision: https://phabricator.services.mozilla.com/D132054
This commit is contained in:
Emilio Cobos Álvarez 2021-11-24 23:58:26 +00:00
Родитель 998283bed3
Коммит eb3a9e6bd7
3 изменённых файлов: 70 добавлений и 82 удалений

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

@ -134,8 +134,7 @@ void ImplCycleCollectionUnlink(ContentIteratorBase& aField) {
ImplCycleCollectionUnlink(aField.mClosestCommonInclusiveAncestor);
}
ContentIteratorBase::ContentIteratorBase(Order aOrder)
: mIsDone(false), mOrder(aOrder) {}
ContentIteratorBase::ContentIteratorBase(Order aOrder) : mOrder(aOrder) {}
ContentIteratorBase::~ContentIteratorBase() = default;
@ -148,8 +147,6 @@ nsresult ContentIteratorBase::Init(nsINode* aRoot) {
return NS_ERROR_NULL_POINTER;
}
mIsDone = false;
if (mOrder == Order::Pre) {
mFirst = aRoot;
mLast = ContentIteratorBase::GetDeepLastChild(aRoot);
@ -166,8 +163,6 @@ nsresult ContentIteratorBase::Init(nsINode* aRoot) {
}
nsresult ContentIteratorBase::Init(nsRange* aRange) {
mIsDone = false;
if (NS_WARN_IF(!aRange)) {
return NS_ERROR_INVALID_ARG;
}
@ -183,8 +178,6 @@ nsresult ContentIteratorBase::Init(nsINode* aStartContainer,
uint32_t aStartOffset,
nsINode* aEndContainer,
uint32_t aEndOffset) {
mIsDone = false;
if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStartContainer, aStartOffset,
aEndContainer, aEndOffset))) {
return NS_ERROR_INVALID_ARG;
@ -196,8 +189,6 @@ nsresult ContentIteratorBase::Init(nsINode* aStartContainer,
nsresult ContentIteratorBase::Init(const RawRangeBoundary& aStart,
const RawRangeBoundary& aEnd) {
mIsDone = false;
if (NS_WARN_IF(!RangeUtils::IsValidPoints(aStart, aEnd))) {
return NS_ERROR_INVALID_ARG;
}
@ -293,12 +284,10 @@ nsresult ContentIteratorBase::Initializer::Run() {
// If either first or last is null, they both have to be null!
if (!mIterator.mFirst || !mIterator.mLast) {
mIterator.mFirst = nullptr;
mIterator.mLast = nullptr;
mIterator.SetEmpty();
}
mIterator.mCurNode = mIterator.mFirst;
mIterator.mIsDone = !mIterator.mCurNode;
return NS_OK;
}
@ -460,7 +449,6 @@ void ContentIteratorBase::SetEmpty() {
mFirst = nullptr;
mLast = nullptr;
mClosestCommonInclusiveAncestor = nullptr;
mIsDone = true;
}
// static
@ -585,12 +573,11 @@ nsINode* ContentIteratorBase::NextNode(nsINode* aNode) {
nsINode* parent = node->GetParentNode();
if (NS_WARN_IF(!parent)) {
MOZ_ASSERT(parent, "The node is the root node but not the last node");
mIsDone = true;
mCurNode = nullptr;
return node;
}
nsIContent* sibling = node->GetNextSibling();
if (sibling) {
if (nsIContent* sibling = node->GetNextSibling()) {
// next node is sibling's "deep left" child
return ContentIteratorBase::GetDeepFirstChild(sibling);
}
@ -606,7 +593,7 @@ nsINode* ContentIteratorBase::PrevNode(nsINode* aNode) {
nsINode* parent = node->GetParentNode();
if (NS_WARN_IF(!parent)) {
MOZ_ASSERT(parent, "The node is the root node but not the first node");
mIsDone = true;
mCurNode = nullptr;
return aNode;
}
@ -632,35 +619,36 @@ nsINode* ContentIteratorBase::PrevNode(nsINode* aNode) {
******************************************************/
void ContentIteratorBase::First() {
if (mFirst) {
mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
if (!mFirst) {
MOZ_ASSERT(IsDone());
mCurNode = nullptr;
return;
}
mIsDone = mFirst == nullptr;
mozilla::DebugOnly<nsresult> rv = PositionAt(mFirst);
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
}
void ContentIteratorBase::Last() {
// Note that mLast can be nullptr if SetEmpty() is called in Init()
// since at that time, Init() returns NS_OK.
if (!mLast) {
MOZ_ASSERT(mIsDone);
MOZ_ASSERT(IsDone());
mCurNode = nullptr;
return;
}
mozilla::DebugOnly<nsresult> rv = PositionAt(mLast);
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to position iterator!");
mIsDone = mLast == nullptr;
}
void ContentIteratorBase::Next() {
if (mIsDone || NS_WARN_IF(!mCurNode)) {
if (IsDone()) {
return;
}
if (mCurNode == mLast) {
mIsDone = true;
mCurNode = nullptr;
return;
}
@ -668,20 +656,18 @@ void ContentIteratorBase::Next() {
}
void ContentIteratorBase::Prev() {
if (NS_WARN_IF(mIsDone) || NS_WARN_IF(!mCurNode)) {
if (IsDone()) {
return;
}
if (mCurNode == mFirst) {
mIsDone = true;
mCurNode = nullptr;
return;
}
mCurNode = PrevNode(mCurNode);
}
bool ContentIteratorBase::IsDone() { return mIsDone; }
// Keeping arrays of indexes for the stack of nodes makes PositionAt
// interesting...
nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) {
@ -691,7 +677,6 @@ nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) {
// take an early out if this doesn't actually change the position
if (mCurNode == aCurNode) {
mIsDone = false;
return NS_OK;
}
mCurNode = aCurNode;
@ -735,24 +720,13 @@ nsresult ContentIteratorBase::PositionAt(nsINode* aCurNode) {
(NS_WARN_IF(!first.IsSet()) || NS_WARN_IF(!last.IsSet()) ||
NS_WARN_IF(!NodeIsInTraversalRange(mCurNode, mOrder == Order::Pre, first,
last)))) {
mIsDone = true;
mCurNode = nullptr;
return NS_ERROR_FAILURE;
}
mIsDone = false;
return NS_OK;
}
nsINode* ContentIteratorBase::GetCurrentNode() {
if (mIsDone) {
return nullptr;
}
NS_ASSERTION(mCurNode, "Null current node in an iterator that's not done!");
return mCurNode;
}
/******************************************************
* ContentSubtreeIterator init routines
******************************************************/
@ -764,8 +738,6 @@ nsresult ContentSubtreeIterator::Init(nsINode* aRoot) {
nsresult ContentSubtreeIterator::Init(nsRange* aRange) {
MOZ_ASSERT(aRange);
mIsDone = false;
if (NS_WARN_IF(!aRange->IsPositioned())) {
return NS_ERROR_INVALID_ARG;
}
@ -785,8 +757,6 @@ nsresult ContentSubtreeIterator::Init(nsINode* aStartContainer,
nsresult ContentSubtreeIterator::Init(const RawRangeBoundary& aStartBoundary,
const RawRangeBoundary& aEndBoundary) {
mIsDone = false;
RefPtr<nsRange> range =
nsRange::Create(aStartBoundary, aEndBoundary, IgnoreErrors());
if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
@ -972,26 +942,18 @@ nsIContent* ContentSubtreeIterator::DetermineLastContent() const {
****************************************************************/
// we can't call PositionAt in a subtree iterator...
void ContentSubtreeIterator::First() {
mIsDone = mFirst == nullptr;
mCurNode = mFirst;
}
void ContentSubtreeIterator::First() { mCurNode = mFirst; }
// we can't call PositionAt in a subtree iterator...
void ContentSubtreeIterator::Last() {
mIsDone = mLast == nullptr;
mCurNode = mLast;
}
void ContentSubtreeIterator::Last() { mCurNode = mLast; }
void ContentSubtreeIterator::Next() {
if (mIsDone || !mCurNode) {
if (IsDone()) {
return;
}
if (mCurNode == mLast) {
mIsDone = true;
mCurNode = nullptr;
return;
}
@ -1013,22 +975,17 @@ void ContentSubtreeIterator::Next() {
}
mCurNode = nextNode;
// This shouldn't be needed, but since our selection code can put us
// in a situation where mLast is in generated content, we need this
// to stop the iterator when we've walked past past the last node!
mIsDone = mCurNode == nullptr;
}
void ContentSubtreeIterator::Prev() {
// Prev should be optimized to use the mStartNodes, just as Next
// uses mInclusiveAncestorsOfEndContainer.
if (mIsDone || !mCurNode) {
if (IsDone()) {
return;
}
if (mCurNode == mFirst) {
mIsDone = true;
mCurNode = nullptr;
return;
}
@ -1041,16 +998,10 @@ void ContentSubtreeIterator::Prev() {
prevNode = ContentIteratorBase::GetDeepLastChild(prevNode);
mCurNode = GetTopAncestorInRange(prevNode);
// This shouldn't be needed, but since our selection code can put us
// in a situation where mFirst is in generated content, we need this
// to stop the iterator when we've walked past past the first node!
mIsDone = mCurNode == nullptr;
}
nsresult ContentSubtreeIterator::PositionAt(nsINode* aCurNode) {
NS_ERROR("Not implemented!");
return NS_ERROR_NOT_IMPLEMENTED;
}

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

@ -51,9 +51,9 @@ class ContentIteratorBase {
virtual void Next();
virtual void Prev();
virtual nsINode* GetCurrentNode();
nsINode* GetCurrentNode() const { return mCurNode; }
virtual bool IsDone();
bool IsDone() const { return !mCurNode; }
virtual nsresult PositionAt(nsINode* aCurNode);
@ -102,8 +102,6 @@ class ContentIteratorBase {
// See <https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor>.
nsCOMPtr<nsINode> mClosestCommonInclusiveAncestor;
bool mIsDone;
const Order mOrder;
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
@ -185,14 +183,14 @@ class ContentSubtreeIterator final : public ContentIteratorBase {
virtual nsresult Init(const RawRangeBoundary& aStartBoundary,
const RawRangeBoundary& aEndBoundary) override;
virtual void Next() override;
virtual void Prev() override;
void Next() override;
void Prev() override;
// Must override these because we don't do PositionAt
virtual void First() override;
void First() override;
// Must override these because we don't do PositionAt
virtual void Last() override;
void Last() override;
virtual nsresult PositionAt(nsINode* aCurNode) override;
nsresult PositionAt(nsINode* aCurNode) override;
friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
ContentSubtreeIterator&, const char*,

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

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Conceptually this is just a crashtest, but we need testharness.js so that testdriver works as expected, see https://github.com/web-platform-tests/wpt/issues/31739 -->
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1739079">
<span id="outer"></span>
<script>
const t = async_test("Shouldn't crash when dragging disabled textarea in shadow dom");
const outer = document.getElementById("outer");
outer.attachShadow({ mode: "open" }).innerHTML = `
<span id="inner" style="pointer-events: none">
<textarea disabled></textarea>
</span>
`;
const inner = outer.shadowRoot.getElementById("inner");
inner.attachShadow({ mode: "open" }).innerHTML = `
<div style="display: flex">
<slot></slot>
</div>
`;
const textarea = outer.shadowRoot.querySelector("textarea");
window.addEventListener("load", t.step_func(function() {
const rect = textarea.getBoundingClientRect();
new test_driver.Actions()
.pointerMove(rect.left + 5, rect.top + 5)
.pointerDown()
.pointerMove(rect.left, + 50, rect.top + 50)
.pointerMove(0, 0)
.pointerUp()
.send()
.then(t.step_func_done(function() {
assert_true(true, "Didn't crash nor hang");
}));
}));
</script>