Bug 1336011 - Fix Crash in InvalidArrayIndex_CRASH in mozilla::EditorBase::DeleteSelectionImpl

* EditorBase shouldn't refer mActionListeners directly in loops because it might be removed during a loop
* Create an alias of the type of mEditorObservers
* Create an alias of the type of mDocStateListeners

Tag #1375
This commit is contained in:
Matt A. Tobin 2020-04-15 01:55:25 -04:00 • committed by Roy Tam
commit 31048968fd
2 changed files with 124 additions and 71 deletions

View file

@ -1385,10 +1385,13 @@ EditorBase::CreateNode(nsIAtom* aTag,
AutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext); AutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillCreateNode(nsDependentAtomString(aTag), listener->WillCreateNode(nsDependentAtomString(aTag),
GetAsDOMNode(aParent), aPosition); GetAsDOMNode(aParent), aPosition);
} }
}
nsCOMPtr<Element> ret; nsCOMPtr<Element> ret;
@ -1402,10 +1405,13 @@ EditorBase::CreateNode(nsIAtom* aTag,
mRangeUpdater.SelAdjCreateNode(aParent, aPosition); mRangeUpdater.SelAdjCreateNode(aParent, aPosition);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidCreateNode(nsDependentAtomString(aTag), GetAsDOMNode(ret), listener->DidCreateNode(nsDependentAtomString(aTag), GetAsDOMNode(ret),
GetAsDOMNode(aParent), aPosition, rv); GetAsDOMNode(aParent), aPosition, rv);
} }
}
return ret.forget(); return ret.forget();
} }
@ -1429,10 +1435,13 @@ EditorBase::InsertNode(nsIContent& aNode,
{ {
AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); AutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(), listener->WillInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
aPosition); aPosition);
} }
}
RefPtr<InsertNodeTransaction> transaction = RefPtr<InsertNodeTransaction> transaction =
CreateTxnForInsertNode(aNode, aParent, aPosition); CreateTxnForInsertNode(aNode, aParent, aPosition);
@ -1440,10 +1449,13 @@ EditorBase::InsertNode(nsIContent& aNode,
mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition); mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(), aPosition, listener->DidInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(), aPosition,
rv); rv);
} }
}
return rv; return rv;
} }
@ -1468,9 +1480,12 @@ EditorBase::SplitNode(nsIContent& aNode,
{ {
AutoRules beginRulesSniffing(this, EditAction::splitNode, nsIEditor::eNext); AutoRules beginRulesSniffing(this, EditAction::splitNode, nsIEditor::eNext);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillSplitNode(aNode.AsDOMNode(), aOffset); listener->WillSplitNode(aNode.AsDOMNode(), aOffset);
} }
}
RefPtr<SplitNodeTransaction> transaction = RefPtr<SplitNodeTransaction> transaction =
CreateTxnForSplitNode(aNode, aOffset); CreateTxnForSplitNode(aNode, aOffset);
@ -1482,10 +1497,13 @@ EditorBase::SplitNode(nsIContent& aNode,
mRangeUpdater.SelAdjSplitNode(aNode, aOffset, newNode); mRangeUpdater.SelAdjSplitNode(aNode, aOffset, newNode);
nsresult rv = aResult.StealNSResult(); nsresult rv = aResult.StealNSResult();
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidSplitNode(aNode.AsDOMNode(), aOffset, GetAsDOMNode(newNode), listener->DidSplitNode(aNode.AsDOMNode(), aOffset, GetAsDOMNode(newNode),
rv); rv);
} }
}
// Note: result might be a success code, so we can't use Throw() to // Note: result might be a success code, so we can't use Throw() to
// set it on aResult. // set it on aResult.
aResult = rv; aResult = rv;
@ -1520,10 +1538,13 @@ EditorBase::JoinNodes(nsINode& aLeftNode,
// Find the number of children of the lefthand node // Find the number of children of the lefthand node
uint32_t oldLeftNodeLen = aLeftNode.Length(); uint32_t oldLeftNodeLen = aLeftNode.Length();
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillJoinNodes(aLeftNode.AsDOMNode(), aRightNode.AsDOMNode(), listener->WillJoinNodes(aLeftNode.AsDOMNode(), aRightNode.AsDOMNode(),
parent->AsDOMNode()); parent->AsDOMNode());
} }
}
nsresult rv = NS_OK; nsresult rv = NS_OK;
RefPtr<JoinNodeTransaction> transaction = RefPtr<JoinNodeTransaction> transaction =
@ -1535,10 +1556,13 @@ EditorBase::JoinNodes(nsINode& aLeftNode,
mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, *parent, offset, mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, *parent, offset,
(int32_t)oldLeftNodeLen); (int32_t)oldLeftNodeLen);
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidJoinNodes(aLeftNode.AsDOMNode(), aRightNode.AsDOMNode(), listener->DidJoinNodes(aLeftNode.AsDOMNode(), aRightNode.AsDOMNode(),
parent->AsDOMNode(), rv); parent->AsDOMNode(), rv);
} }
}
return rv; return rv;
} }
@ -1558,9 +1582,12 @@ EditorBase::DeleteNode(nsINode* aNode)
nsIEditor::ePrevious); nsIEditor::ePrevious);
// save node location for selection updating code. // save node location for selection updating code.
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillDeleteNode(aNode->AsDOMNode()); listener->WillDeleteNode(aNode->AsDOMNode());
} }
}
RefPtr<DeleteNodeTransaction> transaction; RefPtr<DeleteNodeTransaction> transaction;
nsresult rv = CreateTxnForDeleteNode(aNode, getter_AddRefs(transaction)); nsresult rv = CreateTxnForDeleteNode(aNode, getter_AddRefs(transaction));
@ -1568,9 +1595,12 @@ EditorBase::DeleteNode(nsINode* aNode)
rv = DoTransaction(transaction); rv = DoTransaction(transaction);
} }
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidDeleteNode(aNode->AsDOMNode(), rv); listener->DidDeleteNode(aNode->AsDOMNode(), rv);
} }
}
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
return NS_OK; return NS_OK;
@ -1844,7 +1874,7 @@ void
EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification) EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
{ {
// Copy the observers since EditAction()s can modify mEditorObservers. // Copy the observers since EditAction()s can modify mEditorObservers.
nsTArray<mozilla::OwningNonNull<nsIEditorObserver>> observers(mEditorObservers); AutoEditorObserverArray observers(mEditorObservers);
switch (aNotification) { switch (aNotification) {
case eNotifyEditorObserversOfEnd: case eNotifyEditorObserversOfEnd:
mIsInEditAction = false; mIsInEditAction = false;
@ -2505,11 +2535,14 @@ EditorBase::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
} }
// Let listeners know what's up // Let listeners know what's up
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillInsertText( listener->WillInsertText(
static_cast<nsIDOMCharacterData*>(insertedTextNode->AsDOMNode()), static_cast<nsIDOMCharacterData*>(insertedTextNode->AsDOMNode()),
insertedOffset, aStringToInsert); insertedOffset, aStringToInsert);
} }
}
// XXX We may not need these view batches anymore. This is handled at a // XXX We may not need these view batches anymore. This is handled at a
// higher level now I believe. // higher level now I believe.
@ -2518,11 +2551,14 @@ EditorBase::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
EndUpdateViewBatch(); EndUpdateViewBatch();
// let listeners know what happened // let listeners know what happened
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidInsertText( listener->DidInsertText(
static_cast<nsIDOMCharacterData*>(insertedTextNode->AsDOMNode()), static_cast<nsIDOMCharacterData*>(insertedTextNode->AsDOMNode()),
insertedOffset, aStringToInsert, rv); insertedOffset, aStringToInsert, rv);
} }
}
// Added some cruft here for bug 43366. Layout was crashing because we left // Added some cruft here for bug 43366. Layout was crashing because we left
// an empty text node lying around in the document. So I delete empty text // an empty text node lying around in the document. So I delete empty text
@ -2583,8 +2619,7 @@ EditorBase::NotifyDocumentListeners(
return NS_OK; return NS_OK;
} }
nsTArray<OwningNonNull<nsIDocumentStateListener>> AutoDocumentStateListenerArray listeners(mDocStateListeners);
listeners(mDocStateListeners);
nsresult rv = NS_OK; nsresult rv = NS_OK;
switch (aNotificationType) { switch (aNotificationType) {
@ -2656,20 +2691,26 @@ EditorBase::DeleteText(nsGenericDOMDataNode& aCharData,
nsIEditor::ePrevious); nsIEditor::ePrevious);
// Let listeners know what's up // Let listeners know what's up
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->WillDeleteText( listener->WillDeleteText(
static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset, static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
aLength); aLength);
} }
}
nsresult rv = DoTransaction(transaction); nsresult rv = DoTransaction(transaction);
// Let listeners know what happened // Let listeners know what happened
for (auto& listener : mActionListeners) { {
AutoActionListenerArray listeners(mActionListeners);
for (auto& listener : listeners) {
listener->DidDeleteText( listener->DidDeleteText(
static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset, static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
aLength, rv); aLength, rv);
} }
}
return rv; return rv;
} }
@ -4034,24 +4075,29 @@ EditorBase::DeleteSelectionImpl(EDirection aAction,
if (NS_SUCCEEDED(rv)) { if (NS_SUCCEEDED(rv)) {
AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction); AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction);
// Notify nsIEditActionListener::WillDelete[Selection|Text|Node] // Notify nsIEditActionListener::WillDelete[Selection|Text|Node]
{
AutoActionListenerArray listeners(mActionListeners);
if (!deleteNode) { if (!deleteNode) {
for (auto& listener : mActionListeners) { for (auto& listener : listeners) {
listener->WillDeleteSelection(selection); listener->WillDeleteSelection(selection);
} }
} else if (deleteCharData) { } else if (deleteCharData) {
for (auto& listener : mActionListeners) { for (auto& listener : listeners) {
listener->WillDeleteText(deleteCharData, deleteCharOffset, 1); listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
} }
} else { } else {
for (auto& listener : mActionListeners) { for (auto& listener : listeners) {
listener->WillDeleteNode(deleteNode->AsDOMNode()); listener->WillDeleteNode(deleteNode->AsDOMNode());
} }
} }
}
// Delete the specified amount // Delete the specified amount
rv = DoTransaction(transaction); rv = DoTransaction(transaction);
// Notify nsIEditActionListener::DidDelete[Selection|Text|Node] // Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
{
AutoActionListenerArray listeners(mActionListeners);
if (!deleteNode) { if (!deleteNode) {
for (auto& listener : mActionListeners) { for (auto& listener : mActionListeners) {
listener->DidDeleteSelection(selection); listener->DidDeleteSelection(selection);
@ -4066,6 +4112,7 @@ EditorBase::DeleteSelectionImpl(EDirection aAction,
} }
} }
} }
}
return rv; return rv;
} }

View file

@ -987,11 +987,17 @@ protected:
RefPtr<TextComposition> mComposition; RefPtr<TextComposition> mComposition;
// Listens to all low level actions on the doc. // Listens to all low level actions on the doc.
nsTArray<OwningNonNull<nsIEditActionListener>> mActionListeners; typedef AutoTArray<OwningNonNull<nsIEditActionListener>, 5>
AutoActionListenerArray;
AutoActionListenerArray mActionListeners;
// Just notify once per high level change. // Just notify once per high level change.
nsTArray<OwningNonNull<nsIEditorObserver>> mEditorObservers; typedef AutoTArray<OwningNonNull<nsIEditorObserver>, 3>
AutoEditorObserverArray;
AutoEditorObserverArray mEditorObservers;
// Listen to overall doc state (dirty or not, just created, etc.). // Listen to overall doc state (dirty or not, just created, etc.).
nsTArray<OwningNonNull<nsIDocumentStateListener>> mDocStateListeners; typedef AutoTArray<OwningNonNull<nsIDocumentStateListener>, 1>
AutoDocumentStateListenerArray;
AutoDocumentStateListenerArray mDocStateListeners;
// Cached selection for AutoSelectionRestorer. // Cached selection for AutoSelectionRestorer.
SelectionState mSavedSel; SelectionState mSavedSel;