bug 332636 - fix editor's handling of surrogate-pair combining marks with backspace. r=ehsan a=roc

This commit is contained in:
Jonathan Kew 2010-12-16 14:17:41 -08:00
parent 0ff27c2b48
commit 7a31c3e079
13 changed files with 99 additions and 32 deletions

View File

@ -907,7 +907,7 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
PRBool needsStart = PR_FALSE;
switch (aBoundaryType) {
case BOUNDARY_CHAR:
amount = eSelectCharacter;
amount = eSelectCluster;
if (aType == eGetAt)
aType = eGetAfter; // Avoid returning 2 characters
break;

View File

@ -14,8 +14,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=332636
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332636">Mozilla Bug 332636</a>
<p id="display"></p>
<div id="content">
<div id="edit" contenteditable="true">a𐐀b</div>
<div id="edit2" contenteditable="true">a&#x10a0f;b</div>
<div id="edit0" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
<div id="edit1" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
<div id="edit2" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
<div id="edit3" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
<div id="edit0b" contenteditable="true">axb</div><!-- reference: plane 0 base character -->
<div id="edit1b" contenteditable="true">a&#x0308;b</div><!-- reference: plane 0 diacritic -->
<div id="edit2b" contenteditable="true">a&#x10400;b</div><!-- plane 1 base character -->
<div id="edit3b" contenteditable="true">a&#x10a0f;b</div><!-- plane 1 diacritic -->
</div>
<pre id="test">
<script type="application/javascript">
@ -33,9 +40,33 @@ function test(edit) {
is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
}
function testWithMove(edit, offset) {
edit.focus();
var sel = window.getSelection();
sel.collapse(edit.childNodes[0], 0);
var i;
for (i = 0; i < offset; ++i) {
synthesizeKey("VK_RIGHT", {});
synthesizeKey("VK_LEFT", {});
synthesizeKey("VK_RIGHT", {});
}
synthesizeKey("VK_BACK_SPACE", {});
is(edit.textContent, "ab", "The backspace key should delete the UTF-16 surrogate pair correctly");
}
function runTest() {
test(document.getElementById("edit"));
/* test backspace-deletion of the middle character */
test(document.getElementById("edit0"));
test(document.getElementById("edit1"));
test(document.getElementById("edit2"));
test(document.getElementById("edit3"));
/* extra tests with the use of RIGHT and LEFT to get to the right place */
testWithMove(document.getElementById("edit0b"), 2);
testWithMove(document.getElementById("edit1b"), 1);
testWithMove(document.getElementById("edit2b"), 2);
testWithMove(document.getElementById("edit3b"), 1);
SimpleTest.finish();
}

View File

@ -69,7 +69,8 @@ public:
virtual ContentOffsets CalcContentOffsetsFromFramePoint(nsPoint aPoint);
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PeekWordState* aState);
@ -274,7 +275,8 @@ BRFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset)
}
PRBool
BRFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
BRFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters)
{
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
// Keep going. The actual line jumping will stop us.

View File

@ -397,7 +397,8 @@ nsContainerFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset)
}
PRBool
nsContainerFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
nsContainerFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters)
{
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
// Don't allow the caret to stay in an empty (leaf) container frame.

View File

@ -98,7 +98,8 @@ public:
virtual PRBool IsLeaf() const;
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE);
#ifdef DEBUG
NS_IMETHOD List(FILE* out, PRInt32 aIndent) const;

View File

@ -5404,6 +5404,7 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
switch (aPos->mAmount) {
case eSelectCharacter:
case eSelectCluster:
{
PRBool eatingNonRenderableWS = PR_FALSE;
PRBool done = PR_FALSE;
@ -5416,7 +5417,8 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
if (eatingNonRenderableWS)
done = current->PeekOffsetNoAmount(movingInFrameDirection, &offset);
else
done = current->PeekOffsetCharacter(movingInFrameDirection, &offset);
done = current->PeekOffsetCharacter(movingInFrameDirection, &offset,
aPos->mAmount == eSelectCluster);
if (!done) {
result =
@ -5705,7 +5707,8 @@ nsFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset)
}
PRBool
nsFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
nsFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters)
{
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
PRInt32 startOffset = *aOffset;

View File

@ -238,7 +238,8 @@ public:
NS_IMETHOD GetSelectionController(nsPresContext *aPresContext, nsISelectionController **aSelCon);
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PeekWordState *aState);
/**

View File

@ -289,13 +289,18 @@ typedef PRUint64 nsFrameState;
//----------------------------------------------------------------------
enum nsSelectionAmount {
eSelectCharacter = 0,
eSelectWord = 1,
eSelectLine = 2, //previous drawn line in flow.
eSelectBeginLine = 3,
eSelectEndLine = 4,
eSelectNoAmount = 5, //just bounce back current offset.
eSelectParagraph = 6 //select a "paragraph"
eSelectCharacter = 0, // a single Unicode character;
// do not use this (prefer Cluster) unless you
// are really sure it's what you want
eSelectCluster = 1, // a grapheme cluster: this is usually the right
// choice for movement or selection by "character"
// as perceived by the user
eSelectWord = 2,
eSelectLine = 3, // previous drawn line in flow.
eSelectBeginLine = 4,
eSelectEndLine = 5,
eSelectNoAmount = 6, // just bounce back current offset.
eSelectParagraph = 7 // select a "paragraph"
};
enum nsDirection {
@ -2672,11 +2677,15 @@ protected:
* @param aForward [in] Are we moving forward (or backward) in content order.
* @param aOffset [in/out] At what offset into the frame to start looking.
* on output - what offset was reached (whether or not we found a place to stop).
* @param aRespectClusters [in] Whether to restrict result to valid cursor locations
* (between grapheme clusters) - default TRUE maintains "normal" behavior,
* FALSE is used for selection by "code unit" (instead of "character")
* @return PR_TRUE: An appropriate offset was found within this frame,
* and is given by aOffset.
* PR_FALSE: Not found within this frame, need to try the next frame.
*/
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset) = 0;
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE) = 0;
/**
* Search the frame for the next word boundary

View File

@ -166,7 +166,8 @@ nsInlineFrame::IsEmpty()
}
PRBool
nsInlineFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
nsInlineFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters)
{
// Override the implementation in nsFrame, to skip empty inline frames
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");

View File

@ -97,7 +97,8 @@ public:
virtual PRBool IsEmpty();
virtual PRBool IsSelfEmpty();
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE);
// nsIHTMLReflow overrides
virtual void AddInlineMinWidth(nsIRenderingContext *aRenderingContext,

View File

@ -1247,7 +1247,7 @@ nsFrameSelection::MoveCaret(PRUint32 aKeycode,
nsIFrame *theFrame;
PRInt32 currentOffset, frameStart, frameEnd;
if (aAmount == eSelectCharacter || aAmount == eSelectWord)
if (aAmount >= eSelectCharacter && aAmount <= eSelectWord)
{
// For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
// so determine the hint here based on the result frame and offset:
@ -2191,15 +2191,15 @@ nsresult
nsFrameSelection::CharacterMove(PRBool aForward, PRBool aExtend)
{
if (aForward)
return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectCharacter);
return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT, aExtend, eSelectCluster);
else
return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectCharacter);
return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT, aExtend, eSelectCluster);
}
nsresult
nsFrameSelection::CharacterExtendForDelete()
{
return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectCharacter);
return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectCluster);
}
nsresult
@ -5847,7 +5847,7 @@ nsTypedSelection::Modify(const nsAString& aAlter, const nsAString& aDirection,
nsSelectionAmount amount;
PRUint32 keycode;
if (aGranularity.LowerCaseEqualsLiteral("character")) {
amount = eSelectCharacter;
amount = eSelectCluster;
keycode = forward ? (PRUint32) nsIDOMKeyEvent::DOM_VK_RIGHT :
(PRUint32) nsIDOMKeyEvent::DOM_VK_LEFT;
}

View File

@ -169,7 +169,8 @@ public:
SelectionType aType);
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters = PR_TRUE);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PeekWordState* aState);

View File

@ -5580,19 +5580,35 @@ private:
};
static PRBool
IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRun,
IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
PRBool aRespectClusters,
gfxTextRun* aTextRun,
nsIFrame* aFrame)
{
if (aIter.IsOriginalCharSkipped())
return PR_FALSE;
PRUint32 index = aIter.GetSkippedOffset();
if (!aTextRun->IsClusterStart(index))
if (aRespectClusters && !aTextRun->IsClusterStart(index))
return PR_FALSE;
if (index > 0) {
// Check whether the proposed position is in between the two halves of a
// surrogate pair; if so, this is not a valid character boundary.
// (In the case where we are respecting clusters, we won't actually get
// this far because the low surrogate is also marked as non-clusterStart
// so we'll return FALSE above.)
// If the textrun is 8-bit it can't have any surrogates, so we only need
// to check the actual characters if GetTextUnicode() returns non-null.
const PRUnichar *txt = aTextRun->GetTextUnicode();
if (txt && NS_IS_LOW_SURROGATE(txt[index]) &&
NS_IS_HIGH_SURROGATE(txt[index-1]))
return PR_FALSE;
}
return PR_TRUE;
}
PRBool
nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset,
PRBool aRespectClusters)
{
PRInt32 contentLength = GetContentLength();
NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
@ -5617,7 +5633,7 @@ nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
for (PRInt32 i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
i >= trimmed.mStart; --i) {
iter.SetOriginalOffset(i);
if (IsAcceptableCaretPosition(iter, mTextRun, this)) {
if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
*aOffset = i - mContentOffset;
return PR_TRUE;
}
@ -5634,7 +5650,7 @@ nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
for (PRInt32 i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
iter.SetOriginalOffset(i);
if (i == trimmed.GetEnd() ||
IsAcceptableCaretPosition(iter, mTextRun, this)) {
IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
*aOffset = i - mContentOffset;
return PR_TRUE;
}