Bug 1158456 - Remove control characters from composition string, and add dom.compositionevent.allow_control_characters pref to control it. r=masayuki

This commit is contained in:
Tooru Fujisawa 2015-05-01 13:49:29 +09:00
parent 75cd0709f1
commit e108ae29ec
5 changed files with 187 additions and 0 deletions

View File

@ -14,6 +14,7 @@
#include "mozilla/EventDispatcher.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/Preferences.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextEvents.h"
@ -43,6 +44,9 @@ TextComposition::TextComposition(nsPresContext* aPresContext,
, mIsRequestingCancel(false)
, mRequestedToCommitOrCancel(false)
, mWasNativeCompositionEndEventDiscarded(false)
, mAllowControlCharacters(
Preferences::GetBool("dom.compositionevent.allow_control_characters",
false))
{
}
@ -133,6 +137,60 @@ TextComposition::OnCompositionEventDiscarded(
mWasNativeCompositionEndEventDiscarded = true;
}
static inline bool
IsControlChar(uint32_t aCharCode)
{
return aCharCode < ' ' || aCharCode == 0x7F;
}
static size_t
FindFirstControlCharacter(const nsAString& aStr)
{
const char16_t* sourceBegin = aStr.BeginReading();
const char16_t* sourceEnd = aStr.EndReading();
for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) {
if (*source != '\t' && IsControlChar(*source)) {
return source - sourceBegin;
}
}
return -1;
}
static void
RemoveControlCharactersFrom(nsAString& aStr, TextRangeArray* aRanges)
{
size_t firstControlCharOffset = FindFirstControlCharacter(aStr);
if (firstControlCharOffset == (size_t)-1) {
return;
}
nsAutoString copy(aStr);
const char16_t* sourceBegin = copy.BeginReading();
const char16_t* sourceEnd = copy.EndReading();
char16_t* dest = aStr.BeginWriting();
if (NS_WARN_IF(!dest)) {
return;
}
char16_t* curDest = dest + firstControlCharOffset;
size_t i = firstControlCharOffset;
for (const char16_t* source = sourceBegin + firstControlCharOffset;
source < sourceEnd; ++source) {
if (*source == '\t' || !IsControlChar(*source)) {
*curDest = *source;
++curDest;
++i;
} else if (aRanges) {
aRanges->RemoveCharacter(i);
}
}
aStr.SetLength(curDest - dest);
}
void
TextComposition::DispatchCompositionEvent(
WidgetCompositionEvent* aCompositionEvent,
@ -140,6 +198,10 @@ TextComposition::DispatchCompositionEvent(
EventDispatchingCallback* aCallBack,
bool aIsSynthesized)
{
if (!mAllowControlCharacters) {
RemoveControlCharactersFrom(aCompositionEvent->mData,
aCompositionEvent->mRanges);
}
if (aCompositionEvent->message == NS_COMPOSITION_COMMIT_AS_IS) {
NS_ASSERTION(!aCompositionEvent->mRanges,
"mRanges of NS_COMPOSITION_COMMIT_AS_IS should be null");

View File

@ -221,6 +221,13 @@ private:
// discarded by PresShell due to not safe to dispatch events.
bool mWasNativeCompositionEndEventDiscarded;
// Allow control characters appear in composition string.
// When this is false, control characters except
// CHARACTER TABULATION (horizontal tab) are removed from
// both composition string and data attribute of compositionupdate
// and compositionend events.
bool mAllowControlCharacters;
// Hide the default constructor and copy constructor.
TextComposition() {}
TextComposition(const TextComposition& aOther);

View File

@ -4713,3 +4713,10 @@ pref("gfx.vsync.refreshdriver", true);
#ifdef MOZ_SECUREELEMENT
pref("dom.secureelement.enabled", false);
#endif
// Allow control characters appear in composition string.
// When this is false, control characters except
// CHARACTER TABULATION (horizontal tab) are removed from
// both composition string and data attribute of compositionupdate
// and compositionend events.
pref("dom.compositionevent.allow_control_characters", false);

View File

@ -174,6 +174,16 @@ struct TextRange
mRangeType == aOther.mRangeType &&
mRangeStyle == aOther.mRangeStyle;
}
void RemoveCharacter(uint32_t aOffset)
{
if (mStartOffset > aOffset) {
--mStartOffset;
--mEndOffset;
} else if (mEndOffset > aOffset) {
--mEndOffset;
}
}
};
/******************************************************************************
@ -223,6 +233,13 @@ public:
}
return true;
}
void RemoveCharacter(uint32_t aOffset)
{
for (size_t i = 0, len = Length(); i < len; i++) {
ElementAt(i).RemoveCharacter(aOffset);
}
}
};
} // namespace mozilla

View File

@ -3186,6 +3186,99 @@ function runNotRedundantChangeTest()
textarea.removeEventListener("text", handler, true);
}
function runControlCharTest()
{
textarea.focus();
var result = {};
function clearResult()
{
result = { compositionupdate: null, compositionend: null };
}
function handler(aEvent)
{
result[aEvent.type] = aEvent.data;
}
textarea.addEventListener("compositionupdate", handler, true);
textarea.addEventListener("compositionend", handler, true);
textarea.value = "";
var controlChars = String.fromCharCode.apply(null, Object.keys(Array.from({length:0x20}))) + "\x7F";
var allowedChars = "\t";
var data = "AB" + controlChars + "CD" + controlChars + "EF";
var removedData = "AB" + allowedChars + "CD" + allowedChars + "EF";
var DIndex = data.indexOf("D");
var removedDIndex = removedData.indexOf("D");
// input string contains control characters
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": data,
"clauses":
[
{ "length": DIndex,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
{ "length": data.length - DIndex,
"attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
]
},
"caret": { "start": DIndex, "length": 0 }
});
checkSelection(removedDIndex, "", "runControlCharTest", "#1")
is(result.compositionupdate, removedData, "runControlCharTest: control characters in event.data should be removed in compositionupdate event #1");
is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #1");
synthesizeComposition({ type: "compositioncommit", data: data });
is(result.compositionend, removedData, "runControlCharTest: control characters in event.data should be removed in compositionend event #2");
is(textarea.value, removedData, "runControlCharTest: control characters should not appear in textarea #2");
textarea.value = "";
clearResult();
SpecialPowers.setBoolPref("dom.compositionevent.allow_control_characters", true);
// input string contains control characters, allowing control characters
clearResult();
synthesizeCompositionChange(
{ "composition":
{ "string": data,
"clauses":
[
{ "length": DIndex,
"attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
{ "length": data.length - DIndex,
"attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
]
},
"caret": { "start": DIndex, "length": 0 }
});
checkSelection(DIndex - 1 + kLFLen, "", "runControlCharTest", "#3")
is(result.compositionupdate, data, "runControlCharTest: control characters in event.data should not be removed in compositionupdate event #3");
is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #3");
synthesizeComposition({ type: "compositioncommit", data: data });
is(result.compositionend, data, "runControlCharTest: control characters in event.data should not be removed in compositionend event #4");
is(textarea.value, data.replace(/\r/g, "\n"), "runControlCharTest: control characters should appear in textarea #4");
SpecialPowers.clearUserPref("dom.compositionevent.allow_control_characters");
textarea.removeEventListener("compositionupdate", handler, true);
textarea.removeEventListener("compositionend", handler, true);
}
function runRemoveContentTest(aCallback)
{
var events = [];
@ -3760,6 +3853,7 @@ function runTest()
runIsComposingTest();
runRedundantChangeTest();
runNotRedundantChangeTest();
runControlCharTest();
runAsyncForceCommitTest(function () {
runRemoveContentTest(function () {
runFrameTest();