Bug 1425997 - Don't try to remove whitespaces in WSRunObject::ConvertToNBSP() when the text node is changed by mutation observer r=m_kato

WSRunObject::ConvertToNBSP() inserts an NBSP, then, removes following ASCII
whitespaces.  When inserting an NBSP, mutation observer may change the
text node.  In this case, it shouldn't keep working on removing ASCII
whitespaces because it may causes unexpected result.

This patch also renames ConvertToNBSP() and GetAsciiWSBounds() to
InsertNBSPAndRemoveFollowingASCIIWhitespaces() and
GetASCIIWhitespacesBounds() for making their jobs clearer.

MozReview-Commit-ID: TVy9fEKL6p

--HG--
extra : rebase_source : f0bce124055a86caca57334f06c75a46098f69ac
This commit is contained in:
Masayuki Nakano 2017-12-21 19:27:31 +09:00
Родитель 739a0defb2
Коммит 18f7821c7b
4 изменённых файлов: 184 добавлений и 71 удалений

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

@ -33,7 +33,7 @@ namespace mozilla {
using namespace dom;
const char16_t nbsp = 160;
const char16_t kNBSP = 160;
WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
nsINode* aNode,
@ -209,8 +209,10 @@ WSRunObject::InsertBreak(Selection& aSelection,
if (!prevPoint.mTextNode ||
(prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar))) {
// We are at start of non-nbsps. Convert to a single nbsp.
nsresult rv = ConvertToNBSP(thePoint);
NS_ENSURE_SUCCESS(rv, nullptr);
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(thePoint);
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
}
}
}
@ -329,15 +331,15 @@ WSRunObject::InsertText(nsIDocument& aDocument,
// We have a leading space
if (beforeRun) {
if (beforeRun->mType & WSType::leadingWS) {
theString.SetCharAt(nbsp, 0);
theString.SetCharAt(kNBSP, 0);
} else if (beforeRun->mType & WSType::normalWS) {
WSPoint wspoint = GetPreviousCharPoint(pointToInsert.AsRaw());
if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
theString.SetCharAt(nbsp, 0);
theString.SetCharAt(kNBSP, 0);
}
}
} else if (mStartReason & WSType::block || mStartReason == WSType::br) {
theString.SetCharAt(nbsp, 0);
theString.SetCharAt(kNBSP, 0);
}
}
@ -348,15 +350,15 @@ WSRunObject::InsertText(nsIDocument& aDocument,
// We have a leading space
if (afterRun) {
if (afterRun->mType & WSType::trailingWS) {
theString.SetCharAt(nbsp, lastCharIndex);
theString.SetCharAt(kNBSP, lastCharIndex);
} else if (afterRun->mType & WSType::normalWS) {
WSPoint wspoint = GetNextCharPoint(pointToInsert.AsRaw());
if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
theString.SetCharAt(nbsp, lastCharIndex);
theString.SetCharAt(kNBSP, lastCharIndex);
}
}
} else if (mEndReason & WSType::block) {
theString.SetCharAt(nbsp, lastCharIndex);
theString.SetCharAt(kNBSP, lastCharIndex);
}
}
@ -369,7 +371,7 @@ WSRunObject::InsertText(nsIDocument& aDocument,
if (nsCRT::IsAsciiSpace(theString[i])) {
if (prevWS) {
// i - 1 can't be negative because prevWS starts out false
theString.SetCharAt(nbsp, i - 1);
theString.SetCharAt(kNBSP, i - 1);
} else {
prevWS = true;
}
@ -395,7 +397,7 @@ WSRunObject::DeleteWSBackward()
NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
// Easy case, preformatted ws.
if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
nsresult rv =
DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
@ -410,9 +412,9 @@ WSRunObject::DeleteWSBackward()
if (nsCRT::IsAsciiSpace(point.mChar)) {
RefPtr<Text> startNodeText, endNodeText;
int32_t startOffset, endOffset;
GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
getter_AddRefs(startNodeText), &startOffset,
getter_AddRefs(endNodeText), &endOffset);
GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
getter_AddRefs(startNodeText), &startOffset,
getter_AddRefs(endNodeText), &endOffset);
// adjust surrounding ws
nsCOMPtr<nsINode> startNode = startNodeText.get();
@ -432,7 +434,7 @@ WSRunObject::DeleteWSBackward()
return NS_OK;
}
if (point.mChar == nbsp) {
if (point.mChar == kNBSP) {
nsCOMPtr<nsINode> node(point.mTextNode);
// adjust surrounding ws
int32_t startOffset = point.mOffset;
@ -462,7 +464,7 @@ WSRunObject::DeleteWSForward()
NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
// Easy case, preformatted ws.
if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
nsresult rv =
DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
@ -477,9 +479,9 @@ WSRunObject::DeleteWSForward()
if (nsCRT::IsAsciiSpace(point.mChar)) {
RefPtr<Text> startNodeText, endNodeText;
int32_t startOffset, endOffset;
GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
getter_AddRefs(startNodeText), &startOffset,
getter_AddRefs(endNodeText), &endOffset);
GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
getter_AddRefs(startNodeText), &startOffset,
getter_AddRefs(endNodeText), &endOffset);
// Adjust surrounding ws
nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
@ -498,7 +500,7 @@ WSRunObject::DeleteWSForward()
return NS_OK;
}
if (point.mChar == nbsp) {
if (point.mChar == kNBSP) {
nsCOMPtr<nsINode> node(point.mTextNode);
// Adjust surrounding ws
int32_t startOffset = point.mOffset;
@ -543,7 +545,7 @@ WSRunObject::PriorVisibleNode(nsINode* aNode,
if (point.mTextNode && point.mTextNode->Length()) {
*outVisNode = point.mTextNode;
*outVisOffset = point.mOffset + 1;
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
*outType = WSType::normalWS;
} else {
*outType = WSType::text;
@ -584,7 +586,7 @@ WSRunObject::NextVisibleNode(nsINode* aNode,
if (point.mTextNode && point.mTextNode->Length()) {
*outVisNode = point.mTextNode;
*outVisOffset = point.mOffset;
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
*outType = WSType::normalWS;
} else {
*outType = WSType::text;
@ -669,7 +671,7 @@ WSRunObject::GetWSNodes()
}
char16_t theChar = textFrag->CharAt(pos);
if (!nsCRT::IsAsciiSpace(theChar)) {
if (theChar != nbsp) {
if (theChar != kNBSP) {
mStartNode = textNode;
mStartOffset = pos + 1;
mStartReason = WSType::text;
@ -722,7 +724,7 @@ WSRunObject::GetWSNodes()
}
char16_t theChar = textFrag->CharAt(pos);
if (!nsCRT::IsAsciiSpace(theChar)) {
if (theChar != nbsp) {
if (theChar != kNBSP) {
mStartNode = textNode;
mStartOffset = pos + 1;
mStartReason = WSType::text;
@ -777,7 +779,7 @@ WSRunObject::GetWSNodes()
}
char16_t theChar = textFrag->CharAt(pos);
if (!nsCRT::IsAsciiSpace(theChar)) {
if (theChar != nbsp) {
if (theChar != kNBSP) {
mEndNode = textNode;
mEndOffset = pos;
mEndReason = WSType::text;
@ -831,7 +833,7 @@ WSRunObject::GetWSNodes()
}
char16_t theChar = textFrag->CharAt(pos);
if (!nsCRT::IsAsciiSpace(theChar)) {
if (theChar != nbsp) {
if (theChar != kNBSP) {
mEndNode = textNode;
mEndOffset = pos;
mEndReason = WSType::text;
@ -1233,8 +1235,11 @@ WSRunObject::PrepareToDeleteRangePriv(WSRunObject* aEndObject)
// make sure leading char of following ws is an nbsp, so that it will show up
WSPoint point = aEndObject->GetNextCharPoint(aEndObject->Point());
if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
nsresult rv = aEndObject->ConvertToNBSP(point);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv =
aEndObject->InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
}
@ -1253,13 +1258,15 @@ WSRunObject::PrepareToDeleteRangePriv(WSRunObject* aEndObject)
if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
RefPtr<Text> wsStartNode, wsEndNode;
int32_t wsStartOffset, wsEndOffset;
GetAsciiWSBounds(eBoth, mNode, mOffset,
getter_AddRefs(wsStartNode), &wsStartOffset,
getter_AddRefs(wsEndNode), &wsEndOffset);
GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
getter_AddRefs(wsStartNode), &wsStartOffset,
getter_AddRefs(wsEndNode), &wsEndOffset);
point.mTextNode = wsStartNode;
point.mOffset = wsStartOffset;
nsresult rv = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
}
@ -1282,8 +1289,10 @@ WSRunObject::PrepareToSplitAcrossBlocksPriv()
// make sure leading char of following ws is an nbsp, so that it will show up
WSPoint point = GetNextCharPoint(Point());
if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
nsresult rv = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
@ -1294,13 +1303,15 @@ WSRunObject::PrepareToSplitAcrossBlocksPriv()
if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
RefPtr<Text> wsStartNode, wsEndNode;
int32_t wsStartOffset, wsEndOffset;
GetAsciiWSBounds(eBoth, mNode, mOffset,
getter_AddRefs(wsStartNode), &wsStartOffset,
getter_AddRefs(wsEndNode), &wsEndOffset);
GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
getter_AddRefs(wsStartNode), &wsStartOffset,
getter_AddRefs(wsEndNode), &wsEndOffset);
point.mTextNode = wsStartNode;
point.mOffset = wsStartOffset;
nsresult rv = ConvertToNBSP(point);
NS_ENSURE_SUCCESS(rv, rv);
nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
return NS_OK;
@ -1492,27 +1503,42 @@ WSRunObject::GetPreviousCharPoint(const WSPoint &aPoint)
}
nsresult
WSRunObject::ConvertToNBSP(WSPoint aPoint)
WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces(WSPoint aPoint)
{
// MOOSE: this routine needs to be modified to preserve the integrity of the
// wsFragment info.
NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
if (NS_WARN_IF(!aPoint.mTextNode)) {
return NS_ERROR_NULL_POINTER;
}
// First, insert an nbsp
// First, insert an NBSP.
AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
nsAutoString nbspStr(nbsp);
nsresult rv =
mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *aPoint.mTextNode,
aPoint.mOffset, true);
NS_ENSURE_SUCCESS(rv, rv);
mHTMLEditor->InsertTextIntoTextNodeImpl(nsDependentSubstring(&kNBSP, 1),
*aPoint.mTextNode, aPoint.mOffset,
true);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Next, find range of ws it will replace
// Now, the text node may have been modified by mutation observer.
// So, the NBSP may have gone.
if (aPoint.mTextNode->TextDataLength() <= aPoint.mOffset ||
aPoint.mTextNode->GetText()->CharAt(aPoint.mOffset) != kNBSP) {
// This is just preparation of an edit action. Let's return NS_OK.
// XXX Perhaps, we should return another success code which indicates
// mutation observer touched the DOM tree. However, that should
// be returned from each transaction's DoTransaction.
return NS_OK;
}
// Next, find range of whitespaces it will be replaced.
RefPtr<Text> startNode, endNode;
int32_t startOffset = 0, endOffset = 0;
GetAsciiWSBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
getter_AddRefs(startNode), &startOffset,
getter_AddRefs(endNode), &endOffset);
GetASCIIWhitespacesBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
getter_AddRefs(startNode), &startOffset,
getter_AddRefs(endNode), &endOffset);
// Finally, delete that replaced ws, if any
if (startNode) {
@ -1527,13 +1553,13 @@ WSRunObject::ConvertToNBSP(WSPoint aPoint)
}
void
WSRunObject::GetAsciiWSBounds(int16_t aDir,
nsINode* aNode,
int32_t aOffset,
Text** outStartNode,
int32_t* outStartOffset,
Text** outEndNode,
int32_t* outEndOffset)
WSRunObject::GetASCIIWhitespacesBounds(int16_t aDir,
nsINode* aNode,
int32_t aOffset,
Text** outStartNode,
int32_t* outStartOffset,
Text** outEndNode,
int32_t* outEndOffset)
{
MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
outEndOffset);
@ -1761,7 +1787,7 @@ WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
// first check for trailing nbsp
WSPoint thePoint = GetPreviousCharPoint(aRun->EndPoint());
if (thePoint.mTextNode && thePoint.mChar == nbsp) {
if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
// now check that what is to the left of it is compatible with replacing nbsp with space
WSPoint prevPoint = GetPreviousCharPoint(thePoint);
if (prevPoint.mTextNode) {
@ -1843,9 +1869,10 @@ WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
RefPtr<Text> startNode, endNode;
int32_t startOffset, endOffset;
GetAsciiWSBounds(eBoth, prevPoint.mTextNode, prevPoint.mOffset + 1,
getter_AddRefs(startNode), &startOffset,
getter_AddRefs(endNode), &endOffset);
GetASCIIWhitespacesBounds(eBoth, prevPoint.mTextNode,
prevPoint.mOffset + 1,
getter_AddRefs(startNode), &startOffset,
getter_AddRefs(endNode), &endOffset);
// Delete that nbsp
nsresult rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
@ -1858,9 +1885,9 @@ WSRunObject::CheckTrailingNBSPOfRun(WSFragment *aRun)
// Finally, insert that nbsp before the ASCII ws run
AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
nsAutoString nbspStr(nbsp);
rv = htmlEditor->InsertTextIntoTextNodeImpl(nbspStr, *startNode,
startOffset, true);
rv =
htmlEditor->InsertTextIntoTextNodeImpl(nsDependentSubstring(&kNBSP, 1),
*startNode, startOffset, true);
NS_ENSURE_SUCCESS(rv, rv);
}
}
@ -1884,7 +1911,7 @@ WSRunObject::ReplacePreviousNBSPIfUnncessary(WSFragment* aRun,
// inserted object.
bool canConvert = false;
WSPoint thePoint = GetPreviousCharPoint(aPoint);
if (thePoint.mTextNode && thePoint.mChar == nbsp) {
if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
WSPoint prevPoint = GetPreviousCharPoint(thePoint);
if (prevPoint.mTextNode) {
if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
@ -1938,7 +1965,7 @@ WSRunObject::CheckLeadingNBSP(WSFragment* aRun,
// before it. What is before it now will end up before the inserted text.
bool canConvert = false;
WSPoint thePoint = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
if (thePoint.mChar == nbsp) {
if (thePoint.mChar == kNBSP) {
WSPoint tmp = thePoint;
// we want to be after thePoint
tmp.mOffset++;

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

@ -410,10 +410,40 @@ protected:
WSPoint GetNextCharPointInternal(const EditorRawDOMPoint& aPoint);
WSPoint GetPreviousCharPointInternal(const EditorRawDOMPoint& aPoint);
nsresult ConvertToNBSP(WSPoint aPoint);
void GetAsciiWSBounds(int16_t aDir, nsINode* aNode, int32_t aOffset,
dom::Text** outStartNode, int32_t* outStartOffset,
dom::Text** outEndNode, int32_t* outEndOffset);
/**
* InsertNBSPAndRemoveFollowingASCIIWhitespaces() inserts an NBSP first.
* Then, if following characters are ASCII whitespaces, will remove them.
*/
nsresult InsertNBSPAndRemoveFollowingASCIIWhitespaces(WSPoint aPoint);
/**
* GetASCIIWhitespacesBounds() retrieves whitespaces before and/or after the
* point specified by aNode and aOffset.
*
* @param aDir Specify eBefore if you want to scan text backward.
* Specify eAfter if you want to scan text forward.
* Specify eBoth if you want to scan text to both
* direction.
* @param aNode The container node where you want to start to scan
* whitespaces from.
* @param aOffset The offset in aNode where you want to start to scan
* whitespaces from.
* @param outStartNode [out] The container of first ASCII whitespace.
* If there is no whitespaces, returns nullptr.
* @param outStartOffset [out] The offset of first ASCII whitespace in
* outStartNode.
* @param outEndNode [out] The container of last ASCII whitespace.
* If there is no whitespaces, returns nullptr.
* @param outEndOffset [out] The offset of last ASCII whitespace in
* outEndNode.
*/
void GetASCIIWhitespacesBounds(int16_t aDir,
nsINode* aNode,
int32_t aOffset,
dom::Text** outStartNode,
int32_t* outStartOffset,
dom::Text** outEndNode,
int32_t* outEndOffset);
/**
* FindNearestRun() looks for a WSFragment which is closest to specified

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

@ -256,6 +256,7 @@ skip-if = toolkit == 'android' # bug 1315898
[test_bug1394758.html]
[test_bug1399722.html]
[test_bug1409520.html]
[test_bug1425997.html]
[test_CF_HTML_clipboard.html]
subsuite = clipboard

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

@ -0,0 +1,55 @@
<!DOCTYPE html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1425997
-->
<html>
<head>
<title>Test for Bug 1425997</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1425997">Mozilla Bug 1425997</a>
<p id="display"></p>
<div id="content" style="display: none;">
</div>
<div id="editor" contenteditable>
<!-- -->
<span id="inline">foo</span>
</div>
<pre id="test">
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(function() {
let selection = window.getSelection();
let editor = document.getElementById("editor");
let originalContent = editor.innerHTML;
function onCharacterDataModified() {
// Until removing all NBSPs which were inserted by the editor,
// emulates Backspace key with "delete" command.
// When this test is created, the behavior is:
// after 1st delete: "\n<!-- -->&nbsp;&nbsp;\n"
// after 2nd delete: "\n<!-- -->&nbsp;\n"
// after 3rd delete: "\n<!-- -->\n"
while (editor.innerHTML.indexOf("&nbsp;") !== -1) {
document.execCommand("delete", false);
}
}
editor.addEventListener("DOMCharacterDataModified", onCharacterDataModified, { once: true });
editor.focus();
selection.selectAllChildren(document.getElementById("inline"));
document.execCommand('insertHTML', false, 'text');
// This expected result is just same as the result of Chrome.
// If the spec says this is wrong, feel free to change this result.
todo_is(editor.innerHTML, "\n<!-- --><span id=\"inline\">text</span>",
"The 'foo' should be replaced with 'text' and whitespaces before the span element should be removed");
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>