mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1162818 part.5 The offset of nsEditor::InsertTextImpl() should be minimum offset of IME selections if there is r=ehsan
This commit is contained in:
parent
cb43121733
commit
aa07a30e72
@ -26,6 +26,7 @@
|
||||
#include "mozFlushType.h" // for mozFlushType::Flush_Frames
|
||||
#include "mozISpellCheckingEngine.h"
|
||||
#include "mozInlineSpellChecker.h" // for mozInlineSpellChecker
|
||||
#include "mozilla/CheckedInt.h" // for CheckedInt
|
||||
#include "mozilla/IMEStateManager.h" // for IMEStateManager
|
||||
#include "mozilla/Preferences.h" // for Preferences
|
||||
#include "mozilla/dom/Selection.h" // for Selection, etc
|
||||
@ -2274,6 +2275,70 @@ NS_IMETHODIMP nsEditor::ScrollSelectionIntoView(bool aScrollToAnchor)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsEditor::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
|
||||
int32_t& aOffset)
|
||||
{
|
||||
if (aNode->IsNodeOfType(nsINode::eTEXT)) {
|
||||
// There is no "better" insertion point.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPlaintextEditor()) {
|
||||
// We cannot find "better" insertion point in HTML editor.
|
||||
// WARNING: When you add some code to find better node in HTML editor,
|
||||
// you need to call this before calling InsertTextImpl() in
|
||||
// nsHTMLEditRules.
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsINode> node = aNode;
|
||||
int32_t offset = aOffset;
|
||||
|
||||
nsCOMPtr<nsINode> root = GetRoot();
|
||||
if (aNode == root) {
|
||||
// In some cases, aNode is the anonymous DIV, and offset is 0. To avoid
|
||||
// injecting unneeded text nodes, we first look to see if we have one
|
||||
// available. In that case, we'll just adjust node and offset accordingly.
|
||||
if (offset == 0 && node->HasChildren() &&
|
||||
node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
|
||||
aNode = node->GetFirstChild();
|
||||
aOffset = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// In some other cases, aNode is the anonymous DIV, and offset points to the
|
||||
// terminating mozBR. In that case, we'll adjust aInOutNode and
|
||||
// aInOutOffset to the preceding text node, if any.
|
||||
if (offset > 0 && node->GetChildAt(offset - 1) &&
|
||||
node->GetChildAt(offset - 1)->IsNodeOfType(nsINode::eTEXT)) {
|
||||
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
|
||||
aNode = node->GetChildAt(offset - 1);
|
||||
aOffset = static_cast<int32_t>(aNode->Length());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes, aNode is the mozBR element itself. In that case, we'll adjust
|
||||
// the insertion point to the previous text node, if one exists, or to the
|
||||
// parent anonymous DIV.
|
||||
if (nsTextEditUtils::IsMozBR(node) && !offset) {
|
||||
if (node->GetPreviousSibling() &&
|
||||
node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
|
||||
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
|
||||
aNode = node->GetPreviousSibling();
|
||||
aOffset = static_cast<int32_t>(aNode->Length());
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetParentNode() && node->GetParentNode() == root) {
|
||||
aNode = node->GetParentNode();
|
||||
aOffset = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
|
||||
nsCOMPtr<nsINode>* aInOutNode,
|
||||
@ -2286,46 +2351,27 @@ nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
|
||||
|
||||
NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc,
|
||||
NS_ERROR_NULL_POINTER);
|
||||
|
||||
if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsINode> node = *aInOutNode;
|
||||
uint32_t offset = static_cast<uint32_t>(*aInOutOffset);
|
||||
// This method doesn't support over INT32_MAX length text since aInOutOffset
|
||||
// is int32_t*.
|
||||
CheckedInt<int32_t> lengthToInsert(aStringToInsert.Length());
|
||||
NS_ENSURE_TRUE(lengthToInsert.isValid(), NS_ERROR_INVALID_ARG);
|
||||
|
||||
if (!node->IsNodeOfType(nsINode::eTEXT) && IsPlaintextEditor()) {
|
||||
nsCOMPtr<nsINode> root = GetRoot();
|
||||
// In some cases, node is the anonymous DIV, and offset is 0. To avoid
|
||||
// injecting unneeded text nodes, we first look to see if we have one
|
||||
// available. In that case, we'll just adjust node and offset accordingly.
|
||||
if (node == root && offset == 0 && node->HasChildren() &&
|
||||
node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
|
||||
node = node->GetFirstChild();
|
||||
}
|
||||
// In some other cases, node is the anonymous DIV, and offset points to the
|
||||
// terminating mozBR. In that case, we'll adjust aInOutNode and
|
||||
// aInOutOffset to the preceding text node, if any.
|
||||
if (node == root && offset > 0 && node->GetChildAt(offset - 1) &&
|
||||
node->GetChildAt(offset - 1)->IsNodeOfType(nsINode::eTEXT)) {
|
||||
node = node->GetChildAt(offset - 1);
|
||||
offset = node->Length();
|
||||
}
|
||||
// Sometimes, node is the mozBR element itself. In that case, we'll adjust
|
||||
// the insertion point to the previous text node, if one exists, or to the
|
||||
// parent anonymous DIV.
|
||||
if (nsTextEditUtils::IsMozBR(node) && offset == 0) {
|
||||
if (node->GetPreviousSibling() &&
|
||||
node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
|
||||
node = node->GetPreviousSibling();
|
||||
offset = node->Length();
|
||||
} else if (node->GetParentNode() && node->GetParentNode() == root) {
|
||||
node = node->GetParentNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
nsCOMPtr<nsINode> node = *aInOutNode;
|
||||
int32_t offset = *aInOutOffset;
|
||||
|
||||
// In some cases, the node may be the anonymous div elemnt or a mozBR
|
||||
// element. Let's try to look for better insertion point in the nearest
|
||||
// text node if there is.
|
||||
FindBetterInsertionPoint(node, offset);
|
||||
|
||||
nsresult res;
|
||||
if (ShouldHandleIMEComposition()) {
|
||||
CheckedInt<int32_t> newOffset;
|
||||
if (!node->IsNodeOfType(nsINode::eTEXT)) {
|
||||
// create a text node
|
||||
nsRefPtr<nsTextNode> newNode = aDoc->CreateTextNode(EmptyString());
|
||||
@ -2334,18 +2380,24 @@ nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
node = newNode;
|
||||
offset = 0;
|
||||
newOffset = lengthToInsert;
|
||||
} else {
|
||||
newOffset = lengthToInsert + offset;
|
||||
NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
|
||||
}
|
||||
res = InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(),
|
||||
offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
offset += aStringToInsert.Length();
|
||||
offset = newOffset.value();
|
||||
} else {
|
||||
if (node->IsNodeOfType(nsINode::eTEXT)) {
|
||||
CheckedInt<int32_t> newOffset = lengthToInsert + offset;
|
||||
NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
|
||||
// we are inserting text into an existing text node.
|
||||
res = InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(),
|
||||
offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
offset += aStringToInsert.Length();
|
||||
offset = newOffset.value();
|
||||
} else {
|
||||
// we are inserting text into a non-text node. first we have to create a
|
||||
// textnode (this also populates it with the text)
|
||||
@ -2354,12 +2406,12 @@ nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
|
||||
res = InsertNode(*newNode, *node, offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
node = newNode;
|
||||
offset = aStringToInsert.Length();
|
||||
offset = lengthToInsert.value();
|
||||
}
|
||||
}
|
||||
|
||||
*aInOutNode = node;
|
||||
*aInOutOffset = static_cast<int32_t>(offset);
|
||||
*aInOutOffset = offset;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -2371,6 +2423,8 @@ nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
|
||||
{
|
||||
nsRefPtr<EditTxn> txn;
|
||||
bool isIMETransaction = false;
|
||||
int32_t replacedOffset = 0;
|
||||
int32_t replacedLength = 0;
|
||||
// aSuppressIME is used when editor must insert text, yet this text is not
|
||||
// part of the current IME operation. Example: adjusting whitespace around an
|
||||
// IME insertion.
|
||||
@ -2397,6 +2451,11 @@ nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
|
||||
|
||||
txn = CreateTxnForIMEText(aStringToInsert);
|
||||
isIMETransaction = true;
|
||||
// All characters of the composition string will be replaced with
|
||||
// aStringToInsert. So, we need to emulate to remove the composition
|
||||
// string.
|
||||
replacedOffset = mIMETextOffset;
|
||||
replacedLength = mIMETextLength;
|
||||
mIMETextLength = aStringToInsert.Length();
|
||||
} else {
|
||||
txn = CreateTxnForInsertText(aStringToInsert, aTextNode, aOffset);
|
||||
@ -2415,6 +2474,11 @@ nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
|
||||
nsresult res = DoTransaction(txn);
|
||||
EndUpdateViewBatch();
|
||||
|
||||
if (replacedLength) {
|
||||
mRangeUpdater.SelAdjDeleteText(
|
||||
static_cast<nsIDOMCharacterData*>(aTextNode.AsDOMNode()),
|
||||
replacedOffset, replacedLength);
|
||||
}
|
||||
mRangeUpdater.SelAdjInsertText(aTextNode, aOffset, aStringToInsert);
|
||||
|
||||
// let listeners know what happened
|
||||
@ -4722,8 +4786,10 @@ nsEditor::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget)
|
||||
// XXX If selection is changed during reframe, this doesn't work well!
|
||||
nsRange* firstRange = selection->GetRangeAt(0);
|
||||
NS_ENSURE_TRUE(firstRange, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsINode> parentNode = firstRange->GetStartParent();
|
||||
Text* textNode = parentNode->GetAsText();
|
||||
nsCOMPtr<nsINode> startNode = firstRange->GetStartParent();
|
||||
int32_t startOffset = firstRange->StartOffset();
|
||||
FindBetterInsertionPoint(startNode, startOffset);
|
||||
Text* textNode = startNode->GetAsText();
|
||||
MOZ_ASSERT(textNode,
|
||||
"There must be text node if mIMETextLength is larger than 0");
|
||||
if (textNode) {
|
||||
@ -5111,3 +5177,49 @@ nsEditor::GetIsInEditAction(bool* aIsInEditAction)
|
||||
*aIsInEditAction = mIsInEditAction;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
int32_t
|
||||
nsEditor::GetIMESelectionStartOffsetIn(nsINode* aTextNode)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode, "aTextNode must not be nullptr");
|
||||
|
||||
nsCOMPtr<nsISelectionController> selectionController;
|
||||
nsresult rv = GetSelectionController(getter_AddRefs(selectionController));
|
||||
NS_ENSURE_SUCCESS(rv, -1);
|
||||
NS_ENSURE_TRUE(selectionController, -1);
|
||||
|
||||
int32_t minOffset = INT32_MAX;
|
||||
static const SelectionType kIMESelectionTypes[] = {
|
||||
nsISelectionController::SELECTION_IME_RAWINPUT,
|
||||
nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
|
||||
nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
|
||||
nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
|
||||
};
|
||||
for (auto selectionType : kIMESelectionTypes) {
|
||||
nsRefPtr<Selection> selection = GetSelection(selectionType);
|
||||
if (!selection) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t i = 0; i < selection->RangeCount(); i++) {
|
||||
nsRefPtr<nsRange> range = selection->GetRangeAt(i);
|
||||
if (NS_WARN_IF(!range)) {
|
||||
continue;
|
||||
}
|
||||
if (NS_WARN_IF(range->GetStartParent() != aTextNode)) {
|
||||
// ignore the start offset...
|
||||
} else {
|
||||
MOZ_ASSERT(range->StartOffset() >= 0,
|
||||
"start offset shouldn't be negative");
|
||||
minOffset = std::min(minOffset, range->StartOffset());
|
||||
}
|
||||
if (NS_WARN_IF(range->GetEndParent() != aTextNode)) {
|
||||
// ignore the end offset...
|
||||
} else {
|
||||
MOZ_ASSERT(range->EndOffset() >= 0,
|
||||
"start offset shouldn't be negative");
|
||||
minOffset = std::min(minOffset, range->EndOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
return minOffset < INT32_MAX ? minOffset : -1;
|
||||
}
|
||||
|
@ -807,6 +807,19 @@ public:
|
||||
|
||||
virtual already_AddRefed<nsIDOMNode> FindUserSelectAllNode(nsIDOMNode* aNode) { return nullptr; }
|
||||
|
||||
/**
|
||||
* GetIMESelectionStartOffsetIn() returns the start offset of IME selection in
|
||||
* the aTextNode. If there is no IME selection, returns -1.
|
||||
*/
|
||||
int32_t GetIMESelectionStartOffsetIn(nsINode* aTextNode);
|
||||
|
||||
/**
|
||||
* FindBetterInsertionPoint() tries to look for better insertion point which
|
||||
* is typically the nearest text node and offset in it.
|
||||
*/
|
||||
void FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
|
||||
int32_t& aOffset);
|
||||
|
||||
protected:
|
||||
enum Tristate {
|
||||
eTriUnset,
|
||||
|
@ -1333,15 +1333,21 @@ nsHTMLEditRules::WillInsertText(EditAction aAction,
|
||||
if (aAction == EditAction::insertIMEText) {
|
||||
// Right now the nsWSRunObject code bails on empty strings, but IME needs
|
||||
// the InsertTextImpl() call to still happen since empty strings are meaningful there.
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
// If there is one or more IME selections, its minimum offset should be
|
||||
// the insertion point.
|
||||
int32_t IMESelectionOffset =
|
||||
mHTMLEditor->GetIMESelectionStartOffsetIn(selNode);
|
||||
if (IMESelectionOffset >= 0) {
|
||||
selOffset = IMESelectionOffset;
|
||||
}
|
||||
if (inString->IsEmpty())
|
||||
{
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode),
|
||||
&selOffset, doc);
|
||||
}
|
||||
else
|
||||
{
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
|
||||
res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc);
|
||||
}
|
||||
|
@ -725,6 +725,14 @@ nsTextEditRules::WillInsertText(EditAction aAction,
|
||||
|
||||
if (aAction == EditAction::insertIMEText) {
|
||||
NS_ENSURE_STATE(mEditor);
|
||||
// Find better insertion point to insert text.
|
||||
mEditor->FindBetterInsertionPoint(selNode, selOffset);
|
||||
// If there is one or more IME selections, its minimum offset should be
|
||||
// the insertion point.
|
||||
int32_t IMESelectionOffset = mEditor->GetIMESelectionStartOffsetIn(selNode);
|
||||
if (IMESelectionOffset >= 0) {
|
||||
selOffset = IMESelectionOffset;
|
||||
}
|
||||
res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user