Bug 822734 - Make HTMLTextAreaElement handle the mutation changes after all ranges handle them r=smaug

A mutation caused by a call of `Text::SplitText` is handled by 2 method calls,
`CharacterDataChanged` and `ContentInserted`, in `nsRange`.  Therefore,
`nsRange` stores some nodes for the later one, but
`HTMLTextAreaElement::ContentInserted` is called before it and that causes
another mutation which causes calling `nsRange::CharacterDataChanged` again.
Therefore, the assertion detects the recursive call.

For avoiding this issue, `HTMLTextAreaElement` needs to wait that all ranges
handle the mutation first.  Fortunately, `ContentInserted` is called with a
script blocker (*1).  Therefore, `HTMLTextAreaElement` can use script runner
to reset the anonymous subtree.

1. https://searchfox.org/mozilla-central/rev/f1dc2743777711c821d43f9911ee7c4447d60c8e/dom/base/nsINode.cpp#1566,1610

Differential Revision: https://phabricator.services.mozilla.com/D167766
This commit is contained in:
Masayuki Nakano
2023-01-27 04:56:55 +00:00
parent e4397572f5
commit 24a299e231
2 changed files with 36 additions and 4 deletions

View File

@@ -38,6 +38,7 @@
#include "nsReadableUtils.h"
#include "nsStyleConsts.h"
#include "nsTextControlFrame.h"
#include "nsThreadUtils.h"
#include "nsXULControllers.h"
NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea)
@@ -847,10 +848,17 @@ void HTMLTextAreaElement::ContentRemoved(nsIContent* aChild,
void HTMLTextAreaElement::ContentChanged(nsIContent* aContent) {
if (!mValueChanged && mDoneAddingChildren &&
nsContentUtils::IsInSameAnonymousTree(this, aContent)) {
// Hard to say what the reset can trigger, so be safe pending
// further auditing.
nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
Reset();
// We should wait all ranges finish handling the mutation before updating
// the anonymous subtree with a call of Reset.
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"ResetHTMLTextAreaElementIfValueHasNotChangedYet",
[self = RefPtr{this}]() {
// However, if somebody has already changed the value, we don't need
// to keep doing this.
if (!self->mValueChanged) {
self->Reset();
}
}));
}
}