зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
998283bed3
Коммит
eb3a9e6bd7
|
@ -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>
|
Загрузка…
Ссылка в новой задаче