Bug 1217700 part.3 Expose text change, selection change and position change notifications to nsITextInputProcessorCallback with nsITextInputProcessorNotification r=smaug

For testing IMEContentObserver, text change, selection change and position change notifications should be exposed to JS with nsITextInputProcessorNotification.

MozReview-Commit-ID: 3PUhKXRwnAn

--HG--
extra : rebase_source : fce7a73683a2d4811070453629ef48d3ad15c8c8
This commit is contained in:
Masayuki Nakano 2017-04-20 20:17:03 +09:00
parent 7b52d07cd2
commit 3d8bbc6723
3 changed files with 563 additions and 22 deletions

View File

@ -10,6 +10,7 @@
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TextInputProcessor.h"
#include "mozilla/widget/IMEData.h"
#include "nsContentUtils.h"
#include "nsIDocShell.h"
#include "nsIWidget.h"
@ -27,12 +28,35 @@ namespace mozilla {
class TextInputProcessorNotification final :
public nsITextInputProcessorNotification
{
typedef IMENotification::SelectionChangeData SelectionChangeData;
typedef IMENotification::SelectionChangeDataBase SelectionChangeDataBase;
typedef IMENotification::TextChangeData TextChangeData;
typedef IMENotification::TextChangeDataBase TextChangeDataBase;
public:
explicit TextInputProcessorNotification(const char* aType)
: mType(aType)
{
}
explicit TextInputProcessorNotification(
const TextChangeDataBase& aTextChangeData)
: mType("notify-text-change")
, mTextChangeData(aTextChangeData)
{
}
explicit TextInputProcessorNotification(
const SelectionChangeDataBase& aSelectionChangeData)
: mType("notify-selection-change")
, mSelectionChangeData(aSelectionChangeData)
{
// SelectionChangeDataBase::mString still refers nsString instance owned
// by aSelectionChangeData. So, this needs to copy the instance.
nsString* string = new nsString(aSelectionChangeData.String());
mSelectionChangeData.mString = string;
}
NS_DECL_ISUPPORTS
NS_IMETHOD GetType(nsACString& aType) override final
@ -41,11 +65,216 @@ public:
return NS_OK;
}
// "notify-text-change" and "notify-selection-change"
NS_IMETHOD GetOffset(uint32_t* aOffset) override final
{
if (NS_WARN_IF(!aOffset)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aOffset = mSelectionChangeData.mOffset;
return NS_OK;
}
if (IsTextChange()) {
*aOffset = mTextChangeData.mStartOffset;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
// "notify-selection-change"
NS_IMETHOD GetText(nsAString& aText) override final
{
if (IsSelectionChange()) {
aText = mSelectionChangeData.String();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCollapsed(bool* aCollapsed) override final
{
if (NS_WARN_IF(!aCollapsed)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCollapsed = mSelectionChangeData.IsCollapsed();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aLength = mSelectionChangeData.Length();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetReversed(bool* aReversed) override final
{
if (NS_WARN_IF(!aReversed)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aReversed = mSelectionChangeData.mReversed;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetWritingMode(nsACString& aWritingMode) override final
{
if (IsSelectionChange()) {
WritingMode writingMode = mSelectionChangeData.GetWritingMode();
if (!writingMode.IsVertical()) {
aWritingMode.AssignLiteral("horizontal-tb");
} else if (writingMode.IsVerticalLR()) {
aWritingMode.AssignLiteral("vertical-lr");
} else {
aWritingMode.AssignLiteral("vertical-rl");
}
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedByComposition(bool* aCausedByComposition) override final
{
if (NS_WARN_IF(!aCausedByComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCausedByComposition = mSelectionChangeData.mCausedByComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedBySelectionEvent(
bool* aCausedBySelectionEvent) override final
{
if (NS_WARN_IF(!aCausedBySelectionEvent)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aCausedBySelectionEvent = mSelectionChangeData.mCausedBySelectionEvent;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetOccurredDuringComposition(
bool* aOccurredDuringComposition) override final
{
if (NS_WARN_IF(!aOccurredDuringComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsSelectionChange()) {
*aOccurredDuringComposition =
mSelectionChangeData.mOccurredDuringComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
// "notify-text-change"
NS_IMETHOD GetRemovedLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aLength = mTextChangeData.OldLength();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetAddedLength(uint32_t* aLength) override final
{
if (NS_WARN_IF(!aLength)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aLength = mTextChangeData.NewLength();
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetCausedOnlyByComposition(
bool* aCausedOnlyByComposition) override final
{
if (NS_WARN_IF(!aCausedOnlyByComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aCausedOnlyByComposition = mTextChangeData.mCausedOnlyByComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetIncludingChangesDuringComposition(
bool* aIncludingChangesDuringComposition) override final
{
if (NS_WARN_IF(!aIncludingChangesDuringComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aIncludingChangesDuringComposition =
mTextChangeData.mIncludingChangesDuringComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHOD GetIncludingChangesWithoutComposition(
bool* aIncludingChangesWithoutComposition) override final
{
if (NS_WARN_IF(!aIncludingChangesWithoutComposition)) {
return NS_ERROR_INVALID_ARG;
}
if (IsTextChange()) {
*aIncludingChangesWithoutComposition =
mTextChangeData.mIncludingChangesWithoutComposition;
return NS_OK;
}
return NS_ERROR_NOT_AVAILABLE;
}
protected:
~TextInputProcessorNotification() { }
virtual ~TextInputProcessorNotification()
{
if (IsSelectionChange()) {
delete mSelectionChangeData.mString;
mSelectionChangeData.mString = nullptr;
}
}
bool IsTextChange() const
{
return mType.EqualsLiteral("notify-text-change");
}
bool IsSelectionChange() const
{
return mType.EqualsLiteral("notify-selection-change");
}
private:
nsAutoCString mType;
union
{
TextChangeDataBase mTextChangeData;
SelectionChangeDataBase mSelectionChangeData;
};
TextInputProcessorNotification() { }
};
@ -668,6 +897,18 @@ TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
case NOTIFY_IME_OF_BLUR:
notification = new TextInputProcessorNotification("notify-blur");
break;
case NOTIFY_IME_OF_TEXT_CHANGE:
notification = new TextInputProcessorNotification(
aNotification.mTextChangeData);
break;
case NOTIFY_IME_OF_SELECTION_CHANGE:
notification = new TextInputProcessorNotification(
aNotification.mSelectionChangeData);
break;
case NOTIFY_IME_OF_POSITION_CHANGE:
notification = new TextInputProcessorNotification(
"notify-position-change");
break;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -701,8 +942,10 @@ TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
NS_IMETHODIMP_(IMENotificationRequests)
TextInputProcessor::GetIMENotificationRequests()
{
// TextInputProcessor::NotifyIME does not require extra change notifications.
return IMENotificationRequests();
// TextInputProcessor should support all change notifications.
return IMENotificationRequests(
IMENotificationRequests::NOTIFY_TEXT_CHANGE |
IMENotificationRequests::NOTIFY_POSITION_CHANGE);
}
NS_IMETHODIMP_(void)

View File

@ -59,6 +59,8 @@ function onunload()
SimpleTest.finish();
}
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
var iframe = document.getElementById("iframe");
var childWindow = iframe.contentWindow;
var textareaInFrame;
@ -3786,7 +3788,7 @@ function runCommitCompositionTests()
description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
}
function runUnloadTests1(aNextTest)
function runUnloadTests1()
{
var description = "runUnloadTests1(): ";
@ -3808,7 +3810,7 @@ function runUnloadTests1(aNextTest)
iframe.removeEventListener("load", arguments.callee, true);
childWindow = iframe.contentWindow;
textareaInFrame = iframe.contentDocument.getElementById("textarea");
setTimeout(aNextTest, 0);
SimpleTest.executeSoon(continueTest);
}, true);
// The composition should be committed internally. So, another TIP should
@ -3848,7 +3850,7 @@ function runUnloadTests1(aNextTest)
iframe.src = "data:text/html,<body>dummy page</body>";
}
function runUnloadTests2(aNextTest)
function runUnloadTests2()
{
var description = "runUnloadTests2(): ";
@ -3870,7 +3872,7 @@ function runUnloadTests2(aNextTest)
iframe.removeEventListener("load", arguments.callee, true);
childWindow = iframe.contentWindow;
textareaInFrame = iframe.contentDocument.getElementById("textarea");
setTimeout(aNextTest, 0);
SimpleTest.executeSoon(continueTest);
}, true);
input.focus();
@ -3911,7 +3913,7 @@ function runUnloadTests2(aNextTest)
iframe.src = "data:text/html,<body>dummy page</body>";
}
function runCallbackTests(aForTests)
function* runCallbackTests(aForTests)
{
var description = "runCallbackTests(aForTests=" + aForTests + "): ";
@ -3921,8 +3923,12 @@ function runCallbackTests(aForTests)
var TIP = createTIP();
var notifications = [];
var callContinueTest = false;
function callback(aTIP, aNotification)
{
if (aTIP == TIP) {
notifications.push(aNotification);
}
switch (aNotification.type) {
case "request-to-commit":
aTIP.commitComposition();
@ -3931,8 +3937,9 @@ function runCallbackTests(aForTests)
aTIP.cancelComposition();
break;
}
if (aTIP == TIP) {
notifications.push(aNotification);
if (callContinueTest) {
callContinueTest = false;
SimpleTest.executeSoon(continueTest);
}
return true;
}
@ -3948,6 +3955,69 @@ function runCallbackTests(aForTests)
}
}
function waitUntilNotificationsReceived()
{
if (notifications.length > 0) {
SimpleTest.executeSoon(continueTest);
} else {
callContinueTest = true;
}
}
function checkPositionChangeNotification(aNotification, aDescription)
{
is(!aNotification || aNotification.type, "notify-position-change",
aDescription + " should cause position change notification");
}
function checkSelectionChangeNotification(aNotification, aDescription, aExpected)
{
is(aNotification.type, "notify-selection-change",
aDescription + " should cause selection change notification");
if (aNotification.type != "notify-selection-change") {
return;
}
is(aNotification.offset, aExpected.offset,
aDescription + " should cause selection change notification whose offset is " + aExpected.offset);
is(aNotification.text, aExpected.text,
aDescription + " should cause selection change notification whose text is '" + aExpected.text + "'");
is(aNotification.collapsed, aExpected.text.length == 0,
aDescription + " should cause selection change notification whose collapsed is " + (aExpected.text.length == 0));
is(aNotification.length, aExpected.text.length,
aDescription + " should cause selection change notification whose length is " + aExpected.text.length);
is(aNotification.reversed, aExpected.reversed || false,
aDescription + " should cause selection change notification whose reversed is " + (aExpected.reversed || false));
is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb",
aDescription + " should cause selection change notification whose writingMode is '" + (aExpected.writingMode || "horizontal-tb"));
is(aNotification.causedByComposition, aExpected.causedByComposition || false,
aDescription + " should cause selection change notification whose causedByComposition is " + (aExpected.causedByComposition || false));
is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false,
aDescription + " should cause selection change notification whose causedBySelectionEvent is " + (aExpected.causedBySelectionEvent || false));
is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false,
aDescription + " should cause cause selection change notification whose occurredDuringComposition is " + (aExpected.occurredDuringComposition || false));
}
function checkTextChangeNotification(aNotification, aDescription, aExpected)
{
is(aNotification.type, "notify-text-change",
aDescription + " should cause text change notification");
if (aNotification.type != "notify-text-change") {
return;
}
is(aNotification.offset, aExpected.offset,
aDescription + " should cause text change notification whose offset is " + aExpected.offset);
is(aNotification.removedLength, aExpected.removedLength,
aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength);
is(aNotification.addedLength, aExpected.addedLength,
aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength);
is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false,
aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false));
is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false,
aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false));
is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true,
aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true));
}
if (aForTests) {
TIP.beginInputTransactionForTests(window, callback);
} else {
@ -3971,15 +4041,94 @@ function runCallbackTests(aForTests)
dumpUnexpectedNotifications(1);
input.focus();
notifications = [];
TIP.setPendingCompositionString("foo");
TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
TIP.flushPendingComposition();
is(notifications.length, 3,
description + "creating composition string 'foo' should cause 3 notifications");
checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'",
{ offset: 0, removedLength: 0, addedLength: 3,
causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'",
{ offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true });
checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'");
dumpUnexpectedNotifications(3);
notifications = [];
synthesizeMouseAtCenter(input, {});
is(notifications.length, 1,
description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
is(notifications.length, 3,
description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications");
is(notifications[0].type, "request-to-commit",
description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition",
{ offset: 0, removedLength: 3, addedLength: 3,
causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false});
checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition");
dumpUnexpectedNotifications(3);
// XXX On macOS, window.moveBy() doesn't cause notify-position-change.
// Investigate this later (although, we cannot notify position change to
// native IME on macOS).
if (!kIsMac) {
input.focus();
notifications = [];
window.moveBy(0, 10);
yield waitUntilNotificationsReceived();
is(notifications.length, 1,
description + "window.moveBy(0, 10) should cause a notification");
checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)");
dumpUnexpectedNotifications(1);
input.focus();
notifications = [];
window.moveBy(10, 0);
yield waitUntilNotificationsReceived();
is(notifications.length, 1,
description + "window.moveBy(10, 0) should cause a notification");
checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)");
dumpUnexpectedNotifications(1);
}
input.focus();
input.value = "abc"
notifications = [];
input.selectionStart = input.selectionEnd = 0;
yield waitUntilNotificationsReceived();
notifications = [];
var rightArrowKeyEvent =
new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT });
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
is(notifications.length, 1,
description + "ArrowRight key press should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" });
dumpUnexpectedNotifications(1);
notifications = [];
var shiftKeyEvent =
new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT });
var leftArrowKeyEvent =
new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT });
TIP.keydown(shiftKeyEvent);
TIP.keydown(leftArrowKeyEvent);
TIP.keyup(leftArrowKeyEvent);
TIP.keyup(shiftKeyEvent);
is(notifications.length, 1,
description + "ArrowLeft key press with Shift should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true });
dumpUnexpectedNotifications(1);
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
notifications = [];
TIP.keydown(shiftKeyEvent);
TIP.keydown(rightArrowKeyEvent);
TIP.keyup(rightArrowKeyEvent);
TIP.keyup(shiftKeyEvent);
is(notifications.length, 1,
description + "ArrowRight key press with Shift should cause a notification");
checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" });
dumpUnexpectedNotifications(1);
notifications = [];
@ -3996,10 +4145,21 @@ function runCallbackTests(aForTests)
dumpUnexpectedNotifications(1);
}
function runTests()
{
textareaInFrame = iframe.contentDocument.getElementById("textarea");
var gTestContinuation = null;
function continueTest()
{
if (!gTestContinuation) {
gTestContinuation = testBody();
}
var ret = gTestContinuation.next();
if (ret.done) {
finish();
}
}
function* testBody()
{
runBeginInputTransactionMethodTests();
runReleaseTests();
runCompositionTests();
@ -4008,13 +4168,16 @@ function runTests()
runKeyTests();
runErrorTests();
runCommitCompositionTests();
runCallbackTests(false);
runCallbackTests(true);
runUnloadTests1(function () {
runUnloadTests2(function () {
finish();
});
});
yield* runCallbackTests(false);
yield* runCallbackTests(true);
yield runUnloadTests1();
yield runUnloadTests2();
}
function runTests()
{
textareaInFrame = iframe.contentDocument.getElementById("textarea");
continueTest();
}
]]>

View File

@ -44,8 +44,143 @@ interface nsITextInputProcessorNotification : nsISupports
* "notify-blur" (optional)
* This is notified when an editable editor loses focus and Gecko stops
* observing the changes in the content.
*
* "notify-text-change" (optional)
* This is notified when text in the focused editor is modified.
* Some attributes below are available to retrieve the detail.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there is no chance to notify you of some text changes
* safely, this represents all changes as a change.
*
* "notify-selection-change" (optional)
* This is notified when selection in the focused editor is changed.
* Some attributes below are available to retrieve the detail.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there was no chance to notify you of this safely, this
* represents the latest selection change.
*
* "notify-position-change" (optional)
* This is notified when layout is changed in the editor or the window
* is moved.
* IME shouldn't change DOM tree, focus nor something when this is notified.
* Note that when there was no chance to notify you of this safely, this
* represents the latest layout change.
*/
readonly attribute ACString type;
/**
* Be careful, line breakers in the editor are treated as native line
* breakers. I.e., on Windows, a line breaker is "\r\n" (CRLF). On the
* others, it is "\n" (LF). If you want TextInputProcessor to treat line
* breakers on Windows as XP line breakers (LF), please file a bug with
* the reason why you need the behavior.
*/
/**
* This attribute has a valid value when type is "notify-text-change" or
* "notify-selection-change".
* This is offset of the start of modified text range if type is
* "notify-text-change". Or offset of start of selection if type is
* "notify-selection-change".
*/
readonly attribute unsigned long offset;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is selected text. I.e., the length is selected length and
* it's empty if the selection is collapsed.
*/
readonly attribute AString text;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is set to true when the selection is collapsed. Otherwise, false.
*/
readonly attribute boolean collapsed;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This is selected length. I.e., if this is 0, collapsed is set to true.
*/
readonly attribute uint32_t length;
/**
* This attribute has a valid value when type is "notify-selection-change".
* When selection is created from latter point to former point, this is
* set to true. Otherwise, false.
* I.e., if this is true, offset + length is the anchor of selection.
*/
readonly attribute boolean reversed;
/**
* This attribute has a valid value when type is "notify-selection-change".
* This indicates the start of the selection's writing mode.
* The value can be "horizontal-tb", "vertical-rl" or "vertical-lr".
*/
readonly attribute ACString writingMode;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change was caused by composition, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean causedByComposition;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change was caused by selection event, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean causedBySelectionEvent;
/**
* This attribute has a valid value when type is "notify-selection-change".
* If the selection change occurred during composition, this is set to true.
* Otherwise, false.
*/
readonly attribute boolean occurredDuringComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* This is removed text length by the change(s). If this is empty, new text
* was just inserted. Otherwise, the text is replaced with new text.
*/
readonly attribute unsigned long removedLength;
/**
* This attribute has a valid value when type is "notify-text-change".
* This is added text length by the change(s). If this is empty, old text
* was just deleted. Otherwise, the text replaces the old text.
*/
readonly attribute unsigned long addedLength;
/**
* This attribute has a valid value when type is "notify-text-change".
* If the text change(s) was caused only by composition, this is set to true.
* Otherwise, false. I.e., if one of text changes are caused by JS or
* modifying without composition, this is set to false.
*/
readonly attribute boolean causedOnlyByComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* If at least one text change not caused by composition occurred during
* composition, this is set to true. Otherwise, false.
* Note that this is set to false when new change is caused by neither
* composition nor occurred during composition because it's outdated for
* new composition.
* In other words, when text changes not caused by composition occurred
* during composition and it may cause committing composition, this is
* set to true.
*/
readonly attribute boolean includingChangesDuringComposition;
/**
* This attribute has a valid value when type is "notify-text-change".
* If at least one text change occurred when there was no composition, this
* is set to true. Otherwise, false.
*/
readonly attribute boolean includingChangesWithoutComposition;
};
/**