mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 11:58:55 +00:00
Backed out 14 changesets (bug 1892514) for causing bustage on IMEContentObserver.cpp CLOSED TREE
Backed out changeset 56d8807b6c36 (bug 1892514) Backed out changeset 2b9fecca5d45 (bug 1892514) Backed out changeset 153feaf168c8 (bug 1892514) Backed out changeset 6042902c7e2f (bug 1892514) Backed out changeset eb87fcf58d1a (bug 1892514) Backed out changeset d9cf5bfd4c34 (bug 1892514) Backed out changeset e65d0b826f1d (bug 1892514) Backed out changeset 1686b6177ab0 (bug 1892514) Backed out changeset 6ed15cfce6df (bug 1892514) Backed out changeset ae6dd25f6e60 (bug 1892514) Backed out changeset c514cf8a7e6d (bug 1892514) Backed out changeset beebf7370041 (bug 1892514) Backed out changeset dd42ed4c05f9 (bug 1892514) Backed out changeset 71837d871833 (bug 1892514)
This commit is contained in:
parent
470a0f8683
commit
a67f926110
@ -59,8 +59,6 @@ support-files = ["file_navigator_resolve_identity_xrays.xhtml"]
|
|||||||
|
|
||||||
["test_serializer_noscript.html"]
|
["test_serializer_noscript.html"]
|
||||||
|
|
||||||
["test_text_change_notifications_when_inserting_text_containing_line_breaks.html"]
|
|
||||||
|
|
||||||
["test_urgent_start.html"]
|
["test_urgent_start.html"]
|
||||||
skip-if = [ #leaks Bug 1571583
|
skip-if = [ #leaks Bug 1571583
|
||||||
"os == 'win' && debug",
|
"os == 'win' && debug",
|
||||||
|
@ -10,10 +10,6 @@
|
|||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
SimpleTest.waitForFocus(async () => {
|
SimpleTest.waitForFocus(async () => {
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["test.ime_content_observer.assert_invalid_cache", true]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const textarea = document.createElement("textarea");
|
const textarea = document.createElement("textarea");
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
@ -4966,10 +4966,6 @@ async function runTextNotificationChangesDuringNoFrame() {
|
|||||||
|
|
||||||
async function runTests()
|
async function runTests()
|
||||||
{
|
{
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["test.ime_content_observer.assert_invalid_cache", true]],
|
|
||||||
});
|
|
||||||
|
|
||||||
textareaInFrame = iframe.contentDocument.getElementById("textarea");
|
textareaInFrame = iframe.contentDocument.getElementById("textarea");
|
||||||
runBeginInputTransactionMethodTests();
|
runBeginInputTransactionMethodTests();
|
||||||
runReleaseTests();
|
runReleaseTests();
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Test text change notification when inserting text containing line breaks</title>
|
|
||||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<script>
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
SimpleTest.waitForFocus(async () => {
|
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["test.ime_content_observer.assert_invalid_cache", true]],
|
|
||||||
});
|
|
||||||
|
|
||||||
const editingHost = document.querySelector("div[contenteditable]");
|
|
||||||
const TIP = SpecialPowers.Cc["@mozilla.org/text-input-processor;1"].createInstance(
|
|
||||||
SpecialPowers.Ci.nsITextInputProcessor
|
|
||||||
);
|
|
||||||
let textChanges = [];
|
|
||||||
function recTextChanges(aTIP, aNotification) {
|
|
||||||
switch (aNotification.type) {
|
|
||||||
case "request-to-commit":
|
|
||||||
aTIP.commitComposition();
|
|
||||||
break;
|
|
||||||
case "request-to-cancel":
|
|
||||||
aTIP.cancelComposition();
|
|
||||||
break;
|
|
||||||
case "notify-text-change":
|
|
||||||
textChanges.push({
|
|
||||||
offset: aNotification.offset,
|
|
||||||
removedLength: aNotification.removedLength,
|
|
||||||
addedLength: aNotification.addedLength
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
editingHost.focus();
|
|
||||||
// Wait for initializing the HTMLEditor.
|
|
||||||
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
||||||
TIP.beginInputTransactionForTests(window, recTextChanges);
|
|
||||||
|
|
||||||
function stringifyTextChanges(aChanges) {
|
|
||||||
if (!aChanges.length) {
|
|
||||||
return "[]";
|
|
||||||
}
|
|
||||||
function stringifyTextChange(aChange) {
|
|
||||||
return `{ offset: ${aChange.offset}, removedLength: ${aChange.removedLength}, addedLength: ${aChange.addedLength} }`;
|
|
||||||
}
|
|
||||||
let result = "";
|
|
||||||
for (const change of aChanges) {
|
|
||||||
if (result == "") {
|
|
||||||
result = "[ ";
|
|
||||||
} else {
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
result += stringifyTextChange(change);
|
|
||||||
}
|
|
||||||
return result + " ]";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const whiteSpace of ["normal", "pre"]) {
|
|
||||||
editingHost.style.whiteSpace = whiteSpace;
|
|
||||||
for (const collapseAt of [0, 1, 2, 3]) {
|
|
||||||
for (const insertingText of ["\nabc\ndef",
|
|
||||||
"abc\ndef",
|
|
||||||
"abc\ndef\n",
|
|
||||||
"\n\nabc",
|
|
||||||
"abc\n\ndef"]) {
|
|
||||||
editingHost.textContent = "XYZ";
|
|
||||||
getSelection().collapse(editingHost.firstChild, collapseAt);
|
|
||||||
await new Promise(resolve => {
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
||||||
});
|
|
||||||
textChanges = [];
|
|
||||||
document.execCommand("insertText", false, insertingText);
|
|
||||||
await new Promise(resolve => {
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
||||||
});
|
|
||||||
const middleOfText = collapseAt > 0 && collapseAt < "XYZ".length;
|
|
||||||
const firstLineLength = insertingText.indexOf("\n");
|
|
||||||
const rightTextLength = middleOfText
|
|
||||||
// First, the `Text` is split. At this time, the right half is removed from the
|
|
||||||
// `Text` and then, added it with new `Text`. Therefore, there is non-zero removed
|
|
||||||
// length.
|
|
||||||
? "XYZ".length - collapseAt
|
|
||||||
// However, if collapsed at start or end of a `Text`, we don't split the `Text`.
|
|
||||||
: 0;
|
|
||||||
is(
|
|
||||||
stringifyTextChanges(textChanges),
|
|
||||||
stringifyTextChanges([
|
|
||||||
{
|
|
||||||
offset: collapseAt,
|
|
||||||
removedLength: rightTextLength,
|
|
||||||
addedLength: insertingText.length + rightTextLength,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
`white-space:${whiteSpace}: inserting text is "${
|
|
||||||
insertingText.replaceAll("\n", "\\n")
|
|
||||||
}" to offset ${collapseAt} in the Text`
|
|
||||||
);
|
|
||||||
textChanges = [];
|
|
||||||
document.execCommand("undo");
|
|
||||||
await new Promise(resolve => {
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(resolve));
|
|
||||||
});
|
|
||||||
// Finally, removing the inserted `Text` nodes and <br> elements causes joining the
|
|
||||||
// `Text` nodes around the inserted text. Therefore, the offset is always 0. Removed
|
|
||||||
// length is the inserted text length and right text length at first split. Then, added
|
|
||||||
// text length is the right text length.
|
|
||||||
const joinText = middleOfText || (firstLineLength > 0 && collapseAt == "XYZ".length);
|
|
||||||
const removeDataFromText = collapseAt == "XYZ".length && firstLineLength > 0;
|
|
||||||
is(
|
|
||||||
stringifyTextChanges(textChanges),
|
|
||||||
stringifyTextChanges([
|
|
||||||
{
|
|
||||||
offset: collapseAt == "XYZ".length ? collapseAt : 0,
|
|
||||||
removedLength: insertingText.length + (joinText && !removeDataFromText ? "XYZ".length : 0),
|
|
||||||
addedLength: joinText && !removeDataFromText ? "XYZ".length : 0,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
`white-space:${whiteSpace}: undoing inserted text is "${
|
|
||||||
insertingText.replaceAll("\n", "\\n")
|
|
||||||
}" to offset ${collapseAt} in the Text`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SimpleTest.finish();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div contenteditable></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -3128,22 +3128,24 @@ nsresult ContentEventHandler::GetFlatTextLengthInRange(
|
|||||||
if (endPosition.Container()->HasChildren()) {
|
if (endPosition.Container()->HasChildren()) {
|
||||||
// When the end node has some children, move the end position to before
|
// When the end node has some children, move the end position to before
|
||||||
// the open tag of its first child.
|
// the open tag of its first child.
|
||||||
nsIContent* const firstChild = endPosition.Container()->GetFirstChild();
|
nsINode* firstChild = endPosition.Container()->GetFirstChild();
|
||||||
if (NS_WARN_IF(!firstChild)) {
|
if (NS_WARN_IF(!firstChild)) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
endPosition = RawNodePosition::Before(*firstChild);
|
endPosition = RawNodePositionBefore(firstChild, 0u);
|
||||||
} else {
|
} else {
|
||||||
// When the end node is empty, move the end position after the node.
|
// When the end node is empty, move the end position after the node.
|
||||||
if (NS_WARN_IF(!endPosition.Container()->IsContent())) {
|
nsIContent* parentContent = endPosition.Container()->GetParent();
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
nsIContent* const parentContent = endPosition.Container()->GetParent();
|
|
||||||
if (NS_WARN_IF(!parentContent)) {
|
if (NS_WARN_IF(!parentContent)) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
}
|
}
|
||||||
endPosition =
|
Maybe<uint32_t> indexInParent =
|
||||||
RawNodePosition::After(*endPosition.Container()->AsContent());
|
parentContent->ComputeIndexOf(endPosition.Container());
|
||||||
|
if (MOZ_UNLIKELY(NS_WARN_IF(indexInParent.isNothing()))) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
MOZ_ASSERT(*indexInParent != UINT32_MAX);
|
||||||
|
endPosition = RawNodePositionBefore(parentContent, *indexInParent + 1u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,71 +212,12 @@ class MOZ_STACK_CLASS ContentEventHandler {
|
|||||||
bool mAfterOpenTag = true;
|
bool mAfterOpenTag = true;
|
||||||
|
|
||||||
RawNodePosition() = default;
|
RawNodePosition() = default;
|
||||||
MOZ_IMPLICIT RawNodePosition(const RawNodePosition& aOther)
|
explicit RawNodePosition(const RawNodePosition& aOther)
|
||||||
: RawRangeBoundary(aOther),
|
: RawRangeBoundary(aOther),
|
||||||
mAfterOpenTag(aOther.mAfterOpenTag)
|
mAfterOpenTag(aOther.mAfterOpenTag)
|
||||||
// Don't use the copy constructor of mAssertNoGC.
|
// Don't use the copy constructor of mAssertNoGC.
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method returning a RawNodePosition object which points start of
|
|
||||||
* first content of aContainer (first child or first character in the data).
|
|
||||||
* I.e., if aContainer is an element node, the result points before the
|
|
||||||
* first child but after the open tag, e.g., <div>{}abc</div> if aContainer
|
|
||||||
* is the <div>. This is important to understand the difference with the
|
|
||||||
* result of Before().
|
|
||||||
*/
|
|
||||||
static RawNodePosition BeforeFirstContentOf(const nsINode& aContainer) {
|
|
||||||
return RawNodePosition(const_cast<nsINode*>(&aContainer), 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method returning a RawNodePosition object which points after
|
|
||||||
* aContent. I.e., if aContent is an element node, the result points after
|
|
||||||
* its close tag, e.g., `<div>abc</div>{}` if aContent is the <div>.
|
|
||||||
*/
|
|
||||||
static RawNodePosition After(const nsIContent& aContent) {
|
|
||||||
RawNodePosition it(aContent.GetParentNode(),
|
|
||||||
const_cast<nsIContent*>(&aContent));
|
|
||||||
it.mAfterOpenTag = false;
|
|
||||||
return it;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method returning a RawNodePosition object which points end of
|
|
||||||
* aContainer. If aContainer is an element node, the result points before
|
|
||||||
* its close tag, e.g., `<div>abc{}</div>` if aContainer is the <div>.
|
|
||||||
*/
|
|
||||||
static RawNodePosition AtEndOf(const nsINode& aContainer) {
|
|
||||||
return RawNodePosition(const_cast<nsINode*>(&aContainer),
|
|
||||||
aContainer.IsText()
|
|
||||||
? aContainer.AsText()->TextDataLength()
|
|
||||||
: aContainer.GetChildCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method returning a RawNodePosition object which points before
|
|
||||||
* aContent. I.e., if aContent is an element node, the result points
|
|
||||||
* before its open tag, e.g., `{}<div>abc</div>` if aContent is the <div>.
|
|
||||||
* Note that this sets different containers whether aContent is being
|
|
||||||
* removed or not. If aContent is being removed, i.e., this is used in
|
|
||||||
* nsIMutationObserver::ContentRemoved(), aContent is already not a child
|
|
||||||
* of its ex-parent. Therefore, the container becomes aContent, but
|
|
||||||
* indicates that it points before the container with mAfterOpenTag.
|
|
||||||
* On the other hand, if it's not being removed, the container is set to
|
|
||||||
* the parent node of aContent. So, in this case, it points after the
|
|
||||||
* previous sibling of aContent actually.
|
|
||||||
*/
|
|
||||||
static RawNodePosition Before(const nsIContent& aContent) {
|
|
||||||
if (!aContent.IsBeingRemoved()) {
|
|
||||||
return RawNodePosition(aContent.GetParentNode(),
|
|
||||||
aContent.GetPreviousSibling());
|
|
||||||
}
|
|
||||||
RawNodePosition ret(const_cast<nsIContent*>(&aContent), 0u);
|
|
||||||
ret.mAfterOpenTag = false;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
RawNodePosition(nsINode* aContainer, uint32_t aOffset)
|
RawNodePosition(nsINode* aContainer, uint32_t aOffset)
|
||||||
: RawRangeBoundary(aContainer, aOffset) {}
|
: RawRangeBoundary(aContainer, aOffset) {}
|
||||||
|
|
||||||
@ -318,42 +259,44 @@ class MOZ_STACK_CLASS ContentEventHandler {
|
|||||||
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
#endif // #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// RawNodePositionBefore isn't good name if Container() isn't an element node
|
||||||
* Get the flatten text length in the range.
|
// nor Offset() is not 0, though, when Container() is an element node and
|
||||||
* @param aStartPosition Start node and offset in the node of the range.
|
// mOffset is 0, this is treated as before the open tag of Container().
|
||||||
* If the container is an element node, it's
|
struct MOZ_STACK_CLASS RawNodePositionBefore final : public RawNodePosition {
|
||||||
* important to start from before or after its open
|
RawNodePositionBefore(nsINode* aContainer, uint32_t aOffset)
|
||||||
* tag because open tag of some elements causes a
|
: RawNodePosition(aContainer, aOffset) {
|
||||||
* line break in the result. If you need the line
|
mAfterOpenTag = false;
|
||||||
* break, you need to use
|
}
|
||||||
* RawNodePosition::Before().
|
|
||||||
* @param aEndPosition End node and offset in the node of the range.
|
RawNodePositionBefore(nsINode* aContainer, nsIContent* aRef)
|
||||||
* If you don't want to include line break which is
|
: RawNodePosition(aContainer, aRef) {
|
||||||
* caused by the open tag of the container when
|
mAfterOpenTag = false;
|
||||||
* you specify start of an element node, you need
|
}
|
||||||
* to use RawNodePosition::Before().
|
};
|
||||||
* @param aRootElement The root element of the editor or document.
|
|
||||||
* aRootElement won't cause any text including
|
// Get the flatten text length in the range.
|
||||||
* line breaks.
|
// @param aStartPosition Start node and offset in the node of the range.
|
||||||
* @param aLength The result of the flatten text length of the
|
// @param aEndPosition End node and offset in the node of the range.
|
||||||
* range.
|
// @param aRootElement The root element of the editor or document.
|
||||||
* @param aLineBreakType Whether this computes flatten text length with
|
// aRootElement won't cause any text including
|
||||||
* native line breakers on the platform or
|
// line breaks.
|
||||||
* with XP line breaker (\n).
|
// @param aLength The result of the flatten text length of the
|
||||||
* @param aIsRemovingNode Should be true only when this is called from
|
// range.
|
||||||
* nsIMutationObserver::ContentRemoved().
|
// @param aLineBreakType Whether this computes flatten text length with
|
||||||
* When this is true, the container of
|
// native line breakers on the platform or
|
||||||
* aStartPosition should be the removing node and
|
// with XP line breaker (\n).
|
||||||
* points start of it and the container of
|
// @param aIsRemovingNode Should be true only when this is called from
|
||||||
* aEndPosition must be same as the container of
|
// nsIMutationObserver::ContentRemoved().
|
||||||
* aStartPosition and points end of the container.
|
// When this is true, aStartPosition.mNode should
|
||||||
*/
|
// be the root node of removing nodes and mOffset
|
||||||
|
// should be 0 and aEndPosition.mNode should be
|
||||||
|
// same as aStartPosition.mNode and mOffset should
|
||||||
|
// be number of the children of mNode.
|
||||||
static nsresult GetFlatTextLengthInRange(
|
static nsresult GetFlatTextLengthInRange(
|
||||||
const RawNodePosition& aStartPosition,
|
const RawNodePosition& aStartPosition,
|
||||||
const RawNodePosition& aEndPosition, const Element* aRootElement,
|
const RawNodePosition& aEndPosition, const Element* aRootElement,
|
||||||
uint32_t* aLength, LineBreakType aLineBreakType,
|
uint32_t* aLength, LineBreakType aLineBreakType,
|
||||||
bool aIsRemovingNode = false);
|
bool aIsRemovingNode = false);
|
||||||
|
|
||||||
// Computes the native text length between aStartOffset and aEndOffset of
|
// Computes the native text length between aStartOffset and aEndOffset of
|
||||||
// aTextNode.
|
// aTextNode.
|
||||||
static uint32_t GetNativeTextLength(const dom::Text& aTextNode,
|
static uint32_t GetNativeTextLength(const dom::Text& aTextNode,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,6 @@
|
|||||||
#include "mozilla/EditorBase.h"
|
#include "mozilla/EditorBase.h"
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
#include "mozilla/dom/Selection.h"
|
#include "mozilla/dom/Selection.h"
|
||||||
#include "mozilla/dom/Text.h"
|
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
#include "nsIDocShell.h" // XXX Why does only this need to be included here?
|
#include "nsIDocShell.h" // XXX Why does only this need to be included here?
|
||||||
@ -237,6 +236,14 @@ class IMEContentObserver final : public nsStubMutationObserver,
|
|||||||
|
|
||||||
// Following methods manages added nodes during a document change.
|
// Following methods manages added nodes during a document change.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MaybeNotifyIMEOfAddedTextDuringDocumentChange() may send text change
|
||||||
|
* notification caused by the nodes added between mFirstAddedContent in
|
||||||
|
* mFirstAddedContainer and mLastAddedContent in
|
||||||
|
* mLastAddedContainer and forgets the range.
|
||||||
|
*/
|
||||||
|
void MaybeNotifyIMEOfAddedTextDuringDocumentChange();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IsInDocumentChange() returns true while the DOM tree is being modified
|
* IsInDocumentChange() returns true while the DOM tree is being modified
|
||||||
* with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
|
* with mozAutoDocUpdate. E.g., it's being modified by setting innerHTML or
|
||||||
@ -247,7 +254,26 @@ class IMEContentObserver final : public nsStubMutationObserver,
|
|||||||
return mDocumentObserver && mDocumentObserver->IsUpdating();
|
return mDocumentObserver && mDocumentObserver->IsUpdating();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool EditorIsHandlingEditSubAction() const;
|
/**
|
||||||
|
* Forget the range of added nodes during a document change.
|
||||||
|
*/
|
||||||
|
void ClearAddedNodesDuringDocumentChange();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HasAddedNodesDuringDocumentChange() returns true when this stores range
|
||||||
|
* of nodes which were added into the DOM tree during a document change but
|
||||||
|
* have not been sent to IME. Note that this should always return false when
|
||||||
|
* IsInDocumentChange() returns false.
|
||||||
|
*/
|
||||||
|
bool HasAddedNodesDuringDocumentChange() const {
|
||||||
|
return mFirstAddedContainer && mLastAddedContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the passed-in node in aParent is the next node of
|
||||||
|
* mLastAddedContent in pre-order tree traversal of the DOM.
|
||||||
|
*/
|
||||||
|
bool IsNextNodeOfLastAddedNode(nsINode* aParent, nsIContent* aChild) const;
|
||||||
|
|
||||||
void PostFocusSetNotification();
|
void PostFocusSetNotification();
|
||||||
void MaybeNotifyIMEOfFocusSet();
|
void MaybeNotifyIMEOfFocusSet();
|
||||||
@ -263,57 +289,8 @@ class IMEContentObserver final : public nsStubMutationObserver,
|
|||||||
void CancelNotifyingIMEOfPositionChange();
|
void CancelNotifyingIMEOfPositionChange();
|
||||||
void PostCompositionEventHandledNotification();
|
void PostCompositionEventHandledNotification();
|
||||||
|
|
||||||
void ContentAdded(nsINode* aContainer, nsIContent* aFirstContent,
|
void NotifyContentAdded(nsINode* aContainer, nsIContent* aFirstContent,
|
||||||
nsIContent* aLastContent);
|
nsIContent* aLastContent);
|
||||||
|
|
||||||
struct MOZ_STACK_CLASS OffsetAndLengthAdjustments {
|
|
||||||
[[nodiscard]] uint32_t AdjustedOffset(uint32_t aOffset) const {
|
|
||||||
MOZ_ASSERT_IF(mOffsetAdjustment < 0, aOffset >= mOffsetAdjustment);
|
|
||||||
return aOffset + mOffsetAdjustment;
|
|
||||||
}
|
|
||||||
[[nodiscard]] uint32_t AdjustedLength(uint32_t aLength) const {
|
|
||||||
MOZ_ASSERT_IF(mOffsetAdjustment < 0, aLength >= mLengthAdjustment);
|
|
||||||
return aLength + mLengthAdjustment;
|
|
||||||
}
|
|
||||||
[[nodiscard]] uint32_t AdjustedEndOffset(uint32_t aEndOffset) const {
|
|
||||||
MOZ_ASSERT_IF(mOffsetAdjustment + mLengthAdjustment < 0,
|
|
||||||
aEndOffset >= mOffsetAdjustment + mLengthAdjustment);
|
|
||||||
return aEndOffset + (mOffsetAdjustment + mLengthAdjustment);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t mOffsetAdjustment = 0;
|
|
||||||
int64_t mLengthAdjustment = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Posts a text change caused by cached added content in mAddedContentCache.
|
|
||||||
*
|
|
||||||
* @param aOffsetOfFirstContent
|
|
||||||
* Flattened text offset of mFirst. This can be
|
|
||||||
* different value from the computed value in the
|
|
||||||
* current tree. However, in the case,
|
|
||||||
* aAdjustments should have the difference. If this
|
|
||||||
* is Nothing, it's computed with the current DOM.
|
|
||||||
* @param aLengthOfContentNNodes
|
|
||||||
* Flattened text length starting from mFirst and
|
|
||||||
* ending by end of mLast. This can be different
|
|
||||||
* value from the computed value in the current
|
|
||||||
* tree. However, in the case, aAdjustments should
|
|
||||||
* have the difference. If this is Nothing, it's
|
|
||||||
* computed with the current DOM.
|
|
||||||
* @param aAdjustments When aOffsetOfFirstContent and/or
|
|
||||||
* aLengthOfContentNodes are specified different
|
|
||||||
* value(s) from the computed value(s) in the
|
|
||||||
* current DOM, these members should have non-zero
|
|
||||||
* values of the differences.
|
|
||||||
*/
|
|
||||||
void NotifyIMEOfCachedConsecutiveNewNodes(
|
|
||||||
const char* aCallerName,
|
|
||||||
const Maybe<uint32_t>& aOffsetOfFirstContent = Nothing(),
|
|
||||||
const Maybe<uint32_t>& aLengthOfContentNNodes = Nothing(),
|
|
||||||
const OffsetAndLengthAdjustments& aAdjustments =
|
|
||||||
OffsetAndLengthAdjustments{0, 0});
|
|
||||||
|
|
||||||
void ObserveEditableNode();
|
void ObserveEditableNode();
|
||||||
/**
|
/**
|
||||||
* NotifyIMEOfBlur() notifies IME of blur.
|
* NotifyIMEOfBlur() notifies IME of blur.
|
||||||
@ -468,331 +445,71 @@ class IMEContentObserver final : public nsStubMutationObserver,
|
|||||||
RefPtr<DocumentObserver> mDocumentObserver;
|
RefPtr<DocumentObserver> mDocumentObserver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlatTextCache stores length of flattened text starting from start of
|
* FlatTextCache stores flat text length from start of the content to
|
||||||
* the observing node (typically editing host or the anonymous <div> of
|
* mNodeOffset of mContainerNode.
|
||||||
* TextEditor) to:
|
|
||||||
* - end of mContent if it's set (IsCachingToEndOfContent() returns true)
|
|
||||||
* - before first content of mContainerNode if mContent is not set
|
|
||||||
* (IsCachingToStartOfContainer() returns true). In this case, the text
|
|
||||||
* length includes a line break length which is caused by the open tag of
|
|
||||||
* mContainerNode if and only if it's an element node and the open tag causes
|
|
||||||
* a line break.
|
|
||||||
*/
|
*/
|
||||||
struct FlatTextCache {
|
struct FlatTextCache {
|
||||||
public:
|
// mContainerNode and mNode represent a point in DOM tree. E.g.,
|
||||||
explicit FlatTextCache(const char* aInstanceName)
|
// if mContainerNode is a div element, mNode is a child.
|
||||||
: mInstanceName(aInstanceName) {}
|
|
||||||
|
|
||||||
void Clear(const char* aCallerName);
|
|
||||||
|
|
||||||
[[nodiscard]] bool HasCache() const { return !!mContainerNode; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if mFlatTextLength caches flattened text length starting from
|
|
||||||
* start of the observing node to the end of mContent.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool IsCachingToEndOfContent() const {
|
|
||||||
return mContainerNode && mContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if mFlatTextLength caches flattened text length starting from
|
|
||||||
* start of the observing node to the start of mContainerNode. Note that if
|
|
||||||
* mContainerNode is an element and whose open tag causes a line break,
|
|
||||||
* mFlatTextLength includes the line break length too.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool IsCachingToStartOfContainer() const {
|
|
||||||
return mContainerNode && !mContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute flattened text length starting from first content of aRootElement
|
|
||||||
* and ending at end of aContent.
|
|
||||||
*
|
|
||||||
* @param aContent This will be set to mContent which points the
|
|
||||||
* last child content node which participates in
|
|
||||||
* the computed mFlatTextLength.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* (This is required to suppress
|
|
||||||
* ContentEventHandler to generate a line break
|
|
||||||
* caused by open tag of the editable root element
|
|
||||||
* due to not editable. Therefore, we need to call
|
|
||||||
* ContentEventHandler methods with this.)
|
|
||||||
*/
|
|
||||||
[[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeEndOfContent(
|
|
||||||
const char* aCallerName, const nsIContent& aContent,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
void CacheFlatTextLengthBeforeEndOfContent(
|
|
||||||
const char* aCallerName, const nsIContent& aContent,
|
|
||||||
uint32_t aFlatTextLength, const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute flattened text length starting from first content of aRootElement
|
|
||||||
* and ending at start of the first content of aContainer.
|
|
||||||
*
|
|
||||||
* @param aContainer This will be set to mContainer and mContent will
|
|
||||||
* be set to nullptr.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* (This is required to suppress
|
|
||||||
* ContentEventHandler to generate a line break
|
|
||||||
* caused by open tag of the editable root element
|
|
||||||
* due to not editable. Therefore, we need to call
|
|
||||||
* ContentEventHandler methods with this.)
|
|
||||||
*/
|
|
||||||
[[nodiscard]] nsresult ComputeAndCacheFlatTextLengthBeforeFirstContent(
|
|
||||||
const char* aCallerName, const nsINode& aContainer,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
void CacheFlatTextLengthBeforeFirstContent(
|
|
||||||
const char* aCallerName, const nsINode& aContainer,
|
|
||||||
uint32_t aFlatTextLength, const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return flattened text length of aContent. I.e., the length includes a
|
|
||||||
* line break caused by the open tag of aContent if it's an element node.
|
|
||||||
*
|
|
||||||
* @param aRemovingContent The content node which is being removed.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* For avoiding to generate a redundant line break
|
|
||||||
* at open tag of this element, this is required
|
|
||||||
* to call methods of ContentEventHandler.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static Result<uint32_t, nsresult> ComputeTextLengthOfContent(
|
|
||||||
const nsIContent& aContent, const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return flattened text length of starting from first content of
|
|
||||||
* aRootElement and ending at before aContent (if ContentEventHandler
|
|
||||||
* generates a line break at open tag of aContent, the result does not
|
|
||||||
* contain the line break length).
|
|
||||||
*
|
|
||||||
* @param aContent The content node which is immediately after a
|
|
||||||
* content which you want to compute the flattened
|
|
||||||
* text length before end of it.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* For avoiding to generate a redundant line break
|
|
||||||
* at open tag of this element, this is required
|
|
||||||
* to call methods of ContentEventHandler.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
||||||
ComputeTextLengthBeforeContent(const nsIContent& aContent,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return flattened text length starting from first content of aRootElement
|
|
||||||
* and ending at start of the first content of aContainer. This means that
|
|
||||||
* if ContentEventHandler generates a line break at the open tag of
|
|
||||||
* aContainer, the result includes the line break length.
|
|
||||||
* NOTE: The difference from ComputeTextLengthBeforeContent() is, result of
|
|
||||||
* this method includes a line break caused by the open tag of aContainer
|
|
||||||
* if and only if it's an element node and ContentEventHandler generates
|
|
||||||
* a line break for its open tag.
|
|
||||||
*
|
|
||||||
* @param aContainer The container node which you want to compute the
|
|
||||||
* flattened text length before the first content
|
|
||||||
* of.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* For avoiding to generate a redundant line break
|
|
||||||
* at open tag of this element, this is required
|
|
||||||
* to call methods of ContentEventHandler.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
||||||
ComputeTextLengthBeforeFirstContentOf(const nsINode& aContainer,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return flattened text length of starting from start of aStartContent and
|
|
||||||
* ending at end of aEndContent. If ContentEventHandler generates a line
|
|
||||||
* break at open tag of aStartContent, the result includes the line break
|
|
||||||
* length.
|
|
||||||
*
|
|
||||||
* @param aStartContent The first content node of consecutive nodes
|
|
||||||
* which you want to compute flattened text length
|
|
||||||
* starting from.
|
|
||||||
* @param aEndContent The last content node of consecutive nodes
|
|
||||||
* which you want to compute flattened text length
|
|
||||||
* ending at.
|
|
||||||
* @param aRootElement The root element of the editor, i.e., editing
|
|
||||||
* host or the anonymous <div> in a text control.
|
|
||||||
* For avoiding to generate a redundant line break
|
|
||||||
* at open tag of this element, this is required
|
|
||||||
* to call methods of ContentEventHandler.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] static Result<uint32_t, nsresult>
|
|
||||||
ComputeTextLengthStartOfContentToEndOfContent(
|
|
||||||
const nsIContent& aStartContent, const nsIContent& aEndContent,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t GetFlatTextLength() const { return mFlatTextLength; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return text length if it's exactly cached or can compute it quickly from
|
|
||||||
* the cached data. aContent must not be new node which is inserted before
|
|
||||||
* mContent because the cached text length does not include the text length
|
|
||||||
* of aContent in such case.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] Maybe<uint32_t> GetFlatTextLengthBeforeContent(
|
|
||||||
const nsIContent& aContent, const nsIContent* aPreviousSibling,
|
|
||||||
const dom::Element* aRootElement) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return text length before aFirstContent if it's exactly cached or can
|
|
||||||
* compute it quickly from the caching data. This is called when the nodes
|
|
||||||
* between aFirstContent and aLastContent are inserted into the tree.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] Maybe<uint32_t> GetFlatTextOffsetOnInsertion(
|
|
||||||
const nsIContent& aFirstContent, const nsIContent& aLastContent,
|
|
||||||
const dom::Element* aRootElement) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This works only in the debug build and
|
|
||||||
* test.ime_content_observer.assert_valid_cache pref is enabled. This
|
|
||||||
* checks with expensive computation, therefore, the pref is enabled only
|
|
||||||
* when running automated tests for editors.
|
|
||||||
*/
|
|
||||||
void AssertValidCache(const dom::Element* aRootElement) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when content nodes from aFirstContent to aLastContent are added.
|
|
||||||
* aAddedFlatTextLength may be flattened text length from start of
|
|
||||||
* aFirstContent to end of aLastContent if it's computed by the caller.
|
|
||||||
* Note that aFirstContent and aLastContent can be in different container
|
|
||||||
* nodes, but this method is currently called with (maybe indirect) siblings
|
|
||||||
* in the same container.
|
|
||||||
*/
|
|
||||||
void ContentAdded(const char* aCallerName, const nsIContent& aFirstContent,
|
|
||||||
const nsIContent& aLastContent,
|
|
||||||
const Maybe<uint32_t>& aAddedFlatTextLength,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when aContent is removed. aFlatTextLengthOfContent is flattened
|
|
||||||
* text length of aContent.
|
|
||||||
*/
|
|
||||||
void ContentRemoved(const nsIContent& aContent,
|
|
||||||
const nsIContent* aPreviousSibling,
|
|
||||||
uint32_t aFlatTextLengthOfContent,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// mContainerNode is parent node of mContent when it's cached.
|
|
||||||
nsCOMPtr<nsINode> mContainerNode;
|
nsCOMPtr<nsINode> mContainerNode;
|
||||||
// mContent points to the last child which participates in the current
|
// mNode points to the last child which participates in the current
|
||||||
// mFlatTextLength. If this is nullptr, mFlatTextLength means that it
|
// mFlatTextLength. If mNode is null, then that means that the end point for
|
||||||
// length before the first content of mContainerNode, i.e., including the
|
// mFlatTextLength is immediately before the first child of mContainerNode.
|
||||||
// line break of that caused by the open tag of mContainerNode.
|
nsCOMPtr<nsINode> mNode;
|
||||||
nsCOMPtr<nsIContent> mContent;
|
// Length of flat text generated from contents between the start of content
|
||||||
|
// and a child node whose index is mNodeOffset of mContainerNode.
|
||||||
|
uint32_t mFlatTextLength;
|
||||||
|
|
||||||
private:
|
FlatTextCache() : mFlatTextLength(0) {}
|
||||||
// Length of flat text generated from contents between the start of the
|
|
||||||
// observing node (typically editing host or the anonymous <div> of
|
|
||||||
// TextEditor) and the end of mContent.
|
|
||||||
uint32_t mFlatTextLength = 0;
|
|
||||||
MOZ_DEFINE_DBG(FlatTextCache, mContainerNode, mContent, mFlatTextLength);
|
|
||||||
|
|
||||||
const char* mInstanceName;
|
void Clear() {
|
||||||
|
mContainerNode = nullptr;
|
||||||
|
mNode = nullptr;
|
||||||
|
mFlatTextLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache(nsINode* aContainer, nsINode* aNode, uint32_t aFlatTextLength) {
|
||||||
|
MOZ_ASSERT(aContainer, "aContainer must not be null");
|
||||||
|
MOZ_ASSERT(!aNode || aNode->GetParentNode() == aContainer,
|
||||||
|
"aNode must be either null or a child of aContainer");
|
||||||
|
mContainerNode = aContainer;
|
||||||
|
mNode = aNode;
|
||||||
|
mFlatTextLength = aFlatTextLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Match(nsINode* aContainer, nsINode* aNode) const {
|
||||||
|
return aContainer == mContainerNode && aNode == mNode;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
// mEndOfAddedTextCache caches text length from the start of content to
|
||||||
friend std::ostream& operator<<(std::ostream& aStream,
|
// the end of the last added content only while an edit action is being
|
||||||
const FlatTextCache& aCache);
|
// handled by the editor and no other mutation (e.g., removing node)
|
||||||
|
|
||||||
// mEndOfAddedTextCache caches text length from the start of the observing
|
|
||||||
// node to the end of the last added content only while an edit action is
|
|
||||||
// being handled by the editor and no other mutation (e.g., removing node)
|
|
||||||
// occur.
|
// occur.
|
||||||
FlatTextCache mEndOfAddedTextCache = FlatTextCache("mEndOfAddedTextCache");
|
FlatTextCache mEndOfAddedTextCache;
|
||||||
// mStartOfRemovingTextRangeCache caches text length from the start of the
|
// mStartOfRemovingTextRangeCache caches text length from the start of content
|
||||||
// observing node to the start of the last removed content only while an edit
|
// to the start of the last removed content only while an edit action is being
|
||||||
// action is being handled by the editor and no other mutation (e.g., adding
|
// handled by the editor and no other mutation (e.g., adding node) occur.
|
||||||
// node) occur. In other words, this caches text length before end of
|
FlatTextCache mStartOfRemovingTextRangeCache;
|
||||||
// mContent or before first child of mContainerNode.
|
|
||||||
FlatTextCache mStartOfRemovingTextRangeCache =
|
|
||||||
FlatTextCache("mStartOfRemovingTextRangeCache");
|
|
||||||
|
|
||||||
/**
|
// mFirstAddedContainer is parent node of first added node in current
|
||||||
* Caches the DOM node ranges with storing the first node and the last node.
|
// document change. So, this is not nullptr only when a node was added
|
||||||
* This is designed for mAddedContentCache. See comment at declaration of it
|
// during a document change and the change has not been included into
|
||||||
* for the detail.
|
// mTextChangeData yet.
|
||||||
*/
|
// Note that this shouldn't be in cycle collection since this is not nullptr
|
||||||
struct AddedContentCache {
|
// only during a document change.
|
||||||
/**
|
nsCOMPtr<nsINode> mFirstAddedContainer;
|
||||||
* Clear the range. Callers should call this with __FUNCTION__ which will be
|
// mLastAddedContainer is parent node of last added node in current
|
||||||
* used to log which caller did it.
|
// document change. So, this is not nullptr only when a node was added
|
||||||
*/
|
// during a document change and the change has not been included into
|
||||||
void Clear(const char* aCallerName);
|
// mTextChangeData yet.
|
||||||
|
// Note that this shouldn't be in cycle collection since this is not nullptr
|
||||||
|
// only during a document change.
|
||||||
|
nsCOMPtr<nsINode> mLastAddedContainer;
|
||||||
|
|
||||||
[[nodiscard]] bool HasCache() const { return mFirst && mLast; }
|
// mFirstAddedContent is the first node added in mFirstAddedContainer.
|
||||||
|
nsCOMPtr<nsIContent> mFirstAddedContent;
|
||||||
/**
|
// mLastAddedContent is the last node added in mLastAddedContainer;
|
||||||
* Return true if aFirstContent and aLastContent can be merged into the
|
nsCOMPtr<nsIContent> mLastAddedContent;
|
||||||
* cached range. This should be called only when the instance caches
|
|
||||||
* something.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool CanMergeWith(const nsIContent& aFirstContent,
|
|
||||||
const nsIContent& aLastContent,
|
|
||||||
const dom::Element* aRootElement) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if aContent is in the cached range. aContent can be not
|
|
||||||
* a child of the common container of the caching range.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool IsInRange(const nsIContent& aContent,
|
|
||||||
const dom::Element* aRootElement) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to cache the range represented by aFirstContent and aLastContent.
|
|
||||||
* If there is a cache, this will extend the caching range to contain
|
|
||||||
* the new range.
|
|
||||||
*
|
|
||||||
* @return true if cached, otherwise, false.
|
|
||||||
*/
|
|
||||||
bool TryToCache(const nsIContent& aFirstContent,
|
|
||||||
const nsIContent& aLastContent,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when aContent is removed from the DOM. If aContent is the first
|
|
||||||
* node or the last node of the range, this updates the cached range.
|
|
||||||
*
|
|
||||||
* @return true if aContent was in the cached range.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool ContentRemoved(const nsIContent& aContent,
|
|
||||||
const nsIContent* aPreviousSibling,
|
|
||||||
const dom::Element* aRootElement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute offset and length of the cached range before the nodes between
|
|
||||||
* aNewFirstContent and aNewLastContent are inserted.
|
|
||||||
*
|
|
||||||
* @return The first one is offset, the other is length.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] Result<std::pair<uint32_t, uint32_t>, nsresult>
|
|
||||||
ComputeFlatTextRangeBeforeInsertingNewContent(
|
|
||||||
const nsIContent& aNewFirstContent, const nsIContent& aNewLastContent,
|
|
||||||
const dom::Element* aRootElement,
|
|
||||||
OffsetAndLengthAdjustments& aDifferences) const;
|
|
||||||
|
|
||||||
MOZ_DEFINE_DBG(AddedContentCache, mFirst, mLast);
|
|
||||||
|
|
||||||
nsCOMPtr<nsIContent> mFirst;
|
|
||||||
nsCOMPtr<nsIContent> mLast;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Caches the first node and the last node of new inserted nodes while editor
|
|
||||||
// handles an editing command/operation. Therefore, the range is always in
|
|
||||||
// the same container node. So, the range means that the direct siblings
|
|
||||||
// between the first node and the last node are the inserted nodes, but not
|
|
||||||
// yet post a text change notification.
|
|
||||||
// FYI: This is cleared when editor ends handling current edit
|
|
||||||
// operation/command. Therefore, the strong pointers in this member don't
|
|
||||||
// need to be added to the cycle collection.
|
|
||||||
AddedContentCache mAddedContentCache;
|
|
||||||
|
|
||||||
TextChangeData mTextChangeData;
|
TextChangeData mTextChangeData;
|
||||||
|
|
||||||
|
@ -12,10 +12,7 @@
|
|||||||
<div contenteditable id="editor"><p><br></p></div>
|
<div contenteditable id="editor"><p><br></p></div>
|
||||||
<script>
|
<script>
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
SimpleTest.waitForFocus(async function doTests() {
|
SimpleTest.waitForFocus(function doTests() {
|
||||||
await SpecialPowers.pushPrefEnv({
|
|
||||||
set: [["test.ime_content_observer.assert_invalid_cache", true]],
|
|
||||||
});
|
|
||||||
for (let editorId of ["input", "textarea", "editor"]) {
|
for (let editorId of ["input", "textarea", "editor"]) {
|
||||||
let editor = document.getElementById(editorId);
|
let editor = document.getElementById(editorId);
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
defaults pref(test.ime_content_observer.assert_invalid_cache,true)
|
|
||||||
|
|
||||||
load 336081-1.xhtml
|
load 336081-1.xhtml
|
||||||
load 403965-1.xhtml
|
load 403965-1.xhtml
|
||||||
load 428489-1.html
|
load 428489-1.html
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
prefs = ["test.ime_content_observer.assert_invalid_cache=true"]
|
|
||||||
skip-if = ["os == 'android'"]
|
skip-if = ["os == 'android'"]
|
||||||
|
|
||||||
["browser_bug527935.js"]
|
["browser_bug527935.js"]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
prefs = ["test.ime_content_observer.assert_invalid_cache=true"]
|
|
||||||
skip-if = ["os == 'android'"]
|
skip-if = ["os == 'android'"]
|
||||||
|
|
||||||
["test_bug489202.xhtml"]
|
["test_bug489202.xhtml"]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
prefs = [
|
prefs = [
|
||||||
"apz.zoom-to-focused-input.enabled=false",
|
"apz.zoom-to-focused-input.enabled=false",
|
||||||
"test.ime_content_observer.assert_invalid_cache=true",
|
|
||||||
"ui.dragThresholdX=4", # Bug 1873142
|
"ui.dragThresholdX=4", # Bug 1873142
|
||||||
"ui.dragThresholdY=4", # Bug 1873142
|
"ui.dragThresholdY=4", # Bug 1873142
|
||||||
]
|
]
|
||||||
|
@ -15633,13 +15633,6 @@
|
|||||||
value: false
|
value: false
|
||||||
mirror: always
|
mirror: always
|
||||||
|
|
||||||
# Enable assertions in IMEContentObserver::FlatTextCache on debug builds.
|
|
||||||
# If this pref is enabled, DOM mutation becomes much slower.
|
|
||||||
- name: test.ime_content_observer.assert_valid_cache
|
|
||||||
type: bool
|
|
||||||
value: false
|
|
||||||
mirror: always
|
|
||||||
|
|
||||||
- name: test.mousescroll
|
- name: test.mousescroll
|
||||||
type: RelaxedAtomicBool
|
type: RelaxedAtomicBool
|
||||||
value: false
|
value: false
|
||||||
|
@ -1 +0,0 @@
|
|||||||
prefs: [test.ime_content_observer.assert_invalid_cache:true]
|
|
@ -1,3 +0,0 @@
|
|||||||
[delete-content-in-no-data-object.html]
|
|
||||||
expected:
|
|
||||||
if debug: [PASS, CRASH]
|
|
@ -1,2 +1 @@
|
|||||||
prefs: [test.ime_content_observer.assert_invalid_cache:true]
|
|
||||||
leak-threshold: [default:51200]
|
leak-threshold: [default:51200]
|
||||||
|
@ -10214,17 +10214,11 @@ async function runIMEContentObserverTest()
|
|||||||
await waitNotifications;
|
await waitNotifications;
|
||||||
ensureToRemovePrecedingPositionChangeNotification();
|
ensureToRemovePrecedingPositionChangeNotification();
|
||||||
if (isDefaultParagraphSeparatorBlock) {
|
if (isDefaultParagraphSeparatorBlock) {
|
||||||
// First, the text node is split to "aB" and "d", i.e., "d" is removed from the text node.
|
// Splitting current block causes removing "d</block>" and inserting "</block><block>d</block>".
|
||||||
// Then, "d" is added after "aB" as new text node. So, this won't appear as a text change
|
|
||||||
// notification. Next, a padding <br> is inserted between the text node. This does not
|
|
||||||
// appear as a text change notification because of invisible node from ContentEventHandler.
|
|
||||||
// Finally, the <block> is split and remove the unnecessary padding <br> from the left
|
|
||||||
// block. So, one line break caused by the open tag of <block> appears with text length of
|
|
||||||
// "d".
|
|
||||||
checkTextChangeNotification(notifications[0], description, {
|
checkTextChangeNotification(notifications[0], description, {
|
||||||
offset: offsetAtContainer + "aB".length,
|
offset: offsetAtContainer + "aB".length,
|
||||||
removedLength: getNativeText("d").length,
|
removedLength: getNativeText("d\n").length,
|
||||||
addedLength: getNativeText("\nd").length,
|
addedLength: getNativeText("\nd\n").length,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Inserting <br> causes removing "d" and inserting "<br>d"
|
// Inserting <br> causes removing "d" and inserting "<br>d"
|
||||||
@ -10276,17 +10270,11 @@ async function runIMEContentObserverTest()
|
|||||||
await waitNotifications;
|
await waitNotifications;
|
||||||
ensureToRemovePrecedingPositionChangeNotification();
|
ensureToRemovePrecedingPositionChangeNotification();
|
||||||
if (isDefaultParagraphSeparatorBlock) {
|
if (isDefaultParagraphSeparatorBlock) {
|
||||||
// First, the text node is split to "a" and "d" after deleting "B", i.e., "Bd" is removed
|
// Splitting current block causes removing "Bd</block>" and inserting "</block><block>d</block>".
|
||||||
// from the text node. Then, "d" is added after "aB" as new text node. So, this won't
|
|
||||||
// appear as a text change notification. Next, a padding <br> is inserted between the text
|
|
||||||
// node. This does not appear as a text change notification because of invisible node from
|
|
||||||
// ContentEventHandler. Finally, the <block> is split and remove the unnecessary padding
|
|
||||||
// <br> from the left block. So, one line break caused by the open tag of <block> appears
|
|
||||||
// with text length of "d".
|
|
||||||
checkTextChangeNotification(notifications[0], description, {
|
checkTextChangeNotification(notifications[0], description, {
|
||||||
offset: offsetAtContainer + "a".length,
|
offset: offsetAtContainer + "a".length,
|
||||||
removedLength: getNativeText("Bd").length,
|
removedLength: getNativeText("Bd\n").length,
|
||||||
addedLength: getNativeText("\nd").length,
|
addedLength: getNativeText("\nd\n").length,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
checkTextChangeNotification(notifications[0], description, {
|
checkTextChangeNotification(notifications[0], description, {
|
||||||
@ -10530,96 +10518,6 @@ async function runIMEContentObserverTest()
|
|||||||
checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc\n\n").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
|
checkTextChangeNotification(notifications[0], description, { offset: getNativeText("\nabc\n\n").length + offsetAtStart, removedLength: getNativeText("\n\n").length, addedLength: 0 });
|
||||||
checkPositionChangeNotification(notifications[1], description);
|
checkPositionChangeNotification(notifications[1], description);
|
||||||
dumpUnexpectedNotifications(description, 2);
|
dumpUnexpectedNotifications(description, 2);
|
||||||
|
|
||||||
await (async function test_insert_multiple_consecutive_siblings() {
|
|
||||||
aElement.innerHTML = "<p><br></p>";
|
|
||||||
const p = aElement.querySelector("p");
|
|
||||||
sel.collapse(p, 0);
|
|
||||||
await flushNotifications();
|
|
||||||
description = aDescription + "Insert multiple consecutive siblings from DOMNodeInserted event listener";
|
|
||||||
aElement.addEventListener("DOMNodeInserted", () => {
|
|
||||||
const span1 = doc.createElement("span");
|
|
||||||
span1.textContent = "a";
|
|
||||||
p.insertBefore(span1, p.firstChild);
|
|
||||||
const span2 = doc.createElement("span");
|
|
||||||
span2.textContent = "b";
|
|
||||||
p.insertBefore(span2, p.firstChild);
|
|
||||||
const span3 = doc.createElement("span");
|
|
||||||
span3.textContent = "c";
|
|
||||||
p.insertBefore(span3, p.firstChild);
|
|
||||||
const span4 = doc.createElement("span");
|
|
||||||
span4.textContent = "d";
|
|
||||||
p.insertBefore(span4, p.firstChild);
|
|
||||||
// Now, span4 should be cached as first added node and <br> should be
|
|
||||||
// cached as the last added node in IMEContentObserver.
|
|
||||||
// span2 is not adjacent sibling of the caching range boundaries.
|
|
||||||
// Therefore, this requires to compute index of span2 in the <p>.
|
|
||||||
// We want to check the path.
|
|
||||||
const span5 = doc.createElement("span");
|
|
||||||
span5.textContent = "e";
|
|
||||||
span2.insertBefore(span5, span2.firstChild);
|
|
||||||
}, {once: true});
|
|
||||||
waitNotifications = promiseReceiveNotifications();
|
|
||||||
synthesizeKey("KEY_Enter", { shiftKey: true }, win, callback); // For setting the callback to recode notifications
|
|
||||||
ensureToRemovePrecedingPositionChangeNotification();
|
|
||||||
is(
|
|
||||||
aElement.innerHTML,
|
|
||||||
"<p><span>d</span><span>c</span><span><span>e</span>b</span><span>a</span><br><br></p>",
|
|
||||||
description + " <span> elements should be inserted before the new <br>"
|
|
||||||
);
|
|
||||||
checkTextChangeNotification(
|
|
||||||
notifications[0],
|
|
||||||
description,
|
|
||||||
{
|
|
||||||
offset: getNativeText("\n").length + offsetAtStart,
|
|
||||||
removedLength: 0,
|
|
||||||
addedLength: getNativeText("dceba\n").length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
|
|
||||||
await (async function test_insert_multiple_indirect_siblings() {
|
|
||||||
aElement.innerHTML = "<p><span>a</span><br><br></p>";
|
|
||||||
const p = aElement.querySelector("p");
|
|
||||||
sel.collapse(p, 2);
|
|
||||||
await flushNotifications();
|
|
||||||
description = aDescription + "Insert multiple indirect siblings from DOMNodeInserted event listener";
|
|
||||||
aElement.addEventListener("DOMNodeInserted", () => {
|
|
||||||
const span0 = p.querySelector("span");
|
|
||||||
const newBR = p.querySelector("br + br");
|
|
||||||
const span1 = doc.createElement("span");
|
|
||||||
span1.textContent = "b";
|
|
||||||
p.insertBefore(span1, newBR);
|
|
||||||
const span2 = doc.createElement("span");
|
|
||||||
span2.textContent = "c";
|
|
||||||
p.insertBefore(span2, newBR);
|
|
||||||
// Now, span1 should be cached as first added node and <br> should be
|
|
||||||
// cached as the last added node in IMEContentObserver.
|
|
||||||
// Then, inserting new node before the first <br> which is before the
|
|
||||||
// span1 (first added node), it should be
|
|
||||||
const span3 = doc.createElement("span");
|
|
||||||
span3.textContent = "d";
|
|
||||||
span0.insertBefore(span3, span0.firstChild);
|
|
||||||
}, {once: true});
|
|
||||||
waitNotifications = promiseReceiveNotifications();
|
|
||||||
synthesizeKey("KEY_Enter", { shiftKey: true }, win, callback); // For setting the callback to recode notifications
|
|
||||||
ensureToRemovePrecedingPositionChangeNotification();
|
|
||||||
is(
|
|
||||||
aElement.innerHTML,
|
|
||||||
"<p><span><span>d</span>a</span><br><span>b</span><span>c</span><br><br></p>",
|
|
||||||
description + " <span> elements should be inserted before the new <br>"
|
|
||||||
);
|
|
||||||
// However, the text changes should be merged into one notification.
|
|
||||||
checkTextChangeNotification(
|
|
||||||
notifications[0],
|
|
||||||
description,
|
|
||||||
{
|
|
||||||
offset: getNativeText("\n").length + offsetAtStart,
|
|
||||||
removedLength: "a\n".length,
|
|
||||||
addedLength: getNativeText("da\nbc\n").length,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
|
await testWithPlaintextEditor("runIMEContentObserverTest with input element: ", input, false);
|
||||||
@ -10972,10 +10870,7 @@ async function runInputModeTest()
|
|||||||
async function runTest()
|
async function runTest()
|
||||||
{
|
{
|
||||||
await SpecialPowers.pushPrefEnv({
|
await SpecialPowers.pushPrefEnv({
|
||||||
set: [
|
set: [["dom.events.textevent.enabled", true]],
|
||||||
["dom.events.textevent.enabled", true],
|
|
||||||
["test.ime_content_observer.assert_invalid_cache", true],
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
|
window.addEventListener("unload", window.arguments[0].SimpleTest.finish, {once: true, capture: true});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user