Bug 1216427 - part 1 - Ensure a character+VS sequence or a ligated Regional-Indicator flag symbol is deleted as a single unit when backspacing. r=emk

This commit is contained in:
Jonathan Kew 2015-10-26 10:47:16 +00:00
parent 4fb932dfc3
commit 898bd67059
7 changed files with 47 additions and 33 deletions

View File

@ -5,6 +5,7 @@
#include "nsPlaintextEditor.h"
#include "gfxFontUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Selection.h"
@ -600,7 +601,8 @@ nsPlaintextEditor::ExtendSelectionForDelete(Selection* aSelection,
break;
case ePrevious: {
// Only extend the selection where the selection is after a UTF-16
// surrogate pair. For other cases we don't want to do that, in order
// surrogate pair or a variation selector.
// For other cases we don't want to do that, in order
// to make sure that pressing backspace will only delete the last
// typed character.
nsCOMPtr<nsIDOMNode> node;
@ -616,9 +618,11 @@ nsPlaintextEditor::ExtendSelectionForDelete(Selection* aSelection,
result = charData->GetData(data);
NS_ENSURE_SUCCESS(result, result);
if (offset > 1 &&
NS_IS_LOW_SURROGATE(data[offset - 1]) &&
NS_IS_HIGH_SURROGATE(data[offset - 2])) {
if ((offset > 1 &&
NS_IS_LOW_SURROGATE(data[offset - 1]) &&
NS_IS_HIGH_SURROGATE(data[offset - 2])) ||
(offset > 0 &&
gfxFontUtils::IsVarSelector(data[offset - 1]))) {
result = selCont->CharacterExtendForBackspace();
}
}

View File

@ -54,7 +54,7 @@ function test(edit, bsCount) {
for (i = 0; i < bsCount; ++i) {
synthesizeKey("VK_BACK_SPACE", {});
}
todo_is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
}
function testWithMove(edit, offset, bsCount) {
@ -70,7 +70,7 @@ function testWithMove(edit, offset, bsCount) {
for (i = 0; i < bsCount; ++i) {
synthesizeKey("VK_BACK_SPACE", {});
}
todo_is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
is(edit.textContent, "ab", "The backspace key should delete the characters correctly");
}
function runTest() {

View File

@ -573,9 +573,6 @@ gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
// mark all the rest as cluster-continuations
while (aString < iter) {
*glyphs = extendCluster;
if (NS_IS_LOW_SURROGATE(*aString)) {
glyphs->SetIsLowSurrogate();
}
glyphs++;
aString++;
}

View File

@ -741,8 +741,7 @@ public:
FLAG_CHAR_IS_TAB = 0x08,
FLAG_CHAR_IS_NEWLINE = 0x10,
FLAG_CHAR_IS_LOW_SURROGATE = 0x20,
CHAR_IDENTITY_FLAGS_MASK = 0x38,
CHAR_IDENTITY_FLAGS_MASK = 0x18,
GLYPH_COUNT_MASK = 0x00FFFF00U,
GLYPH_COUNT_SHIFT = 8
@ -793,9 +792,6 @@ public:
bool CharIsNewline() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE) != 0;
}
bool CharIsLowSurrogate() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_LOW_SURROGATE) != 0;
}
uint32_t CharIdentityFlags() const {
return IsSimpleGlyph() ? 0 : (mValue & CHAR_IDENTITY_FLAGS_MASK);
@ -870,10 +866,6 @@ public:
NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
mValue |= FLAG_CHAR_IS_NEWLINE;
}
void SetIsLowSurrogate() {
NS_ASSERTION(!IsSimpleGlyph(), "Expected non-simple-glyph");
mValue |= FLAG_CHAR_IS_LOW_SURROGATE;
}
private:
uint32_t mValue;
@ -908,11 +900,6 @@ public:
GetCharacterGlyphs()[aIndex].SetIsSpace();
}
void SetIsLowSurrogate(uint32_t aIndex) {
SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
GetCharacterGlyphs()[aIndex].SetIsLowSurrogate();
}
bool HasDetailedGlyphs() const {
return mDetailedGlyphs != nullptr;
}

View File

@ -903,6 +903,16 @@ public:
(ch >= kUnicodeVS17 && ch <= kUnicodeVS256);
}
enum {
kUnicodeRegionalIndicatorA = 0x1F1E6,
kUnicodeRegionalIndicatorZ = 0x1F1FF
};
static inline bool IsRegionalIndicator(uint32_t aCh) {
return aCh >= kUnicodeRegionalIndicatorA &&
aCh <= kUnicodeRegionalIndicatorZ;
}
static inline bool IsInvalid(uint32_t ch) {
return (ch == 0xFFFD);
}

View File

@ -128,10 +128,6 @@ public:
NS_ASSERTION(aPos < GetLength(), "aPos out of range");
return mCharacterGlyphs[aPos].CharIsNewline();
}
bool CharIsLowSurrogate(uint32_t aPos) const {
NS_ASSERTION(aPos < GetLength(), "aPos out of range");
return mCharacterGlyphs[aPos].CharIsLowSurrogate();
}
// All uint32_t aStart, uint32_t aLength ranges below are restricted to
// grapheme cluster boundaries! All offsets are in terms of the string
@ -535,10 +531,6 @@ public:
}
g->SetIsNewline();
}
void SetIsLowSurrogate(uint32_t aIndex) {
SetGlyphs(aIndex, CompressedGlyph().SetComplex(false, false, 0), nullptr);
mCharacterGlyphs[aIndex].SetIsLowSurrogate();
}
/**
* Prefetch all the glyph extents needed to ensure that Measure calls

View File

@ -7026,13 +7026,37 @@ IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
return 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.
// surrogate pair, or before a Variation Selector character;
// 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 (aTextRun->CharIsLowSurrogate(index)) {
uint32_t offs = aIter.GetOriginalOffset();
const nsTextFragment* frag = aFrame->GetContent()->GetText();
uint32_t ch = frag->CharAt(offs);
if (gfxFontUtils::IsVarSelector(ch) ||
(NS_IS_LOW_SURROGATE(ch) && offs > 0 &&
NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1)))) {
return false;
}
// If the proposed position is before a high surrogate, we need to decode
// the surrogate pair (if valid) and check the resulting character.
if (NS_IS_HIGH_SURROGATE(ch) && offs + 1 < frag->GetLength()) {
uint32_t ch2 = frag->CharAt(offs + 1);
if (NS_IS_LOW_SURROGATE(ch2)) {
ch = SURROGATE_TO_UCS4(ch, ch2);
// If the character is a (Plane-14) variation selector,
// or a Regional Indicator character that is ligated with the previous
// character, this is not a valid boundary.
if (gfxFontUtils::IsVarSelector(ch) ||
(gfxFontUtils::IsRegionalIndicator(ch) &&
!aTextRun->IsLigatureGroupStart(index))) {
return false;
}
}
}
}
return true;
}