Bug 1415509 - part 1: EditorBase::FindBetterInsertionPoint() should take an EditorRawDOMPoint argument for input and return EditorRawDOMPoint for the result r=m_kato

EditorBase::FindBetterInsertionPoint() now use 3 in/out arguments.  This is
really ugly and making the callers hard to read.  So, let's make it take an
argument whose type is |const EditorRawDOMPoint&| and return other
EditorRawDOMPoint instance.

Additionally, this fixes bugs of text node length checks in the method.
Basically, this shouldn't affect to any actual behavior, though.  That is
because text node shouldn't be able to have string longer than INT32_MAX.

MozReview-Commit-ID: FClUQSJzd8c

--HG--
extra : rebase_source : 3e2fbd345015f7068c7e35a94c31731e7936009f
This commit is contained in:
Masayuki Nakano 2017-11-08 21:55:10 +09:00
parent 4a945299ed
commit 7adce402c2
4 changed files with 155 additions and 130 deletions

View File

@ -2384,23 +2384,18 @@ EditorBase::ScrollSelectionIntoView(bool aScrollToAnchor)
return NS_OK;
}
void
EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
int32_t& aOffset)
EditorRawDOMPoint
EditorBase::FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
FindBetterInsertionPoint(node, aOffset, nullptr);
aNode = do_QueryInterface(node);
}
if (NS_WARN_IF(!aPoint.IsSet())) {
return aPoint;
}
void
EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
int32_t& aOffset,
nsCOMPtr<nsIContent>* aSelChild)
{
if (aNode->IsNodeOfType(nsINode::eTEXT)) {
MOZ_ASSERT(aPoint.IsSetAndValid());
if (aPoint.Container()->IsNodeOfType(nsINode::eTEXT)) {
// There is no "better" insertion point.
return;
return aPoint;
}
if (!IsPlaintextEditor()) {
@ -2408,57 +2403,44 @@ EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
// WARNING: When you add some code to find better node in HTML editor,
// you need to call this before calling InsertTextImpl() in
// HTMLEditRules.
return;
return aPoint;
}
nsCOMPtr<nsINode> node = aNode;
int32_t offset = aOffset;
nsCOMPtr<nsINode> root = GetRoot();
if (aNode == root) {
if (aPoint.Container() == 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 && node->HasChildren() &&
node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
aNode = node->GetFirstChild();
aOffset = 0;
if (aSelChild) {
*aSelChild = nullptr;
}
return;
if (aPoint.IsStartOfContainer() &&
aPoint.Container()->HasChildren() &&
aPoint.Container()->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
return EditorRawDOMPoint(aPoint.Container()->GetFirstChild(), 0);
}
// 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) {
if (!aPoint.IsStartOfContainer()) {
if (AsHTMLEditor()) {
// Fall back to a slow path that uses GetChildAt() for Thunderbird's
// plaintext editor.
nsIContent* child = node->GetChildAt(offset - 1);
nsIContent* child = aPoint.GetPreviousSiblingOfChildAtOffset();
if (child && child->IsNodeOfType(nsINode::eTEXT)) {
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
aNode = child;
aOffset = static_cast<int32_t>(aNode->Length());
if (aSelChild) {
*aSelChild = nullptr;
if (NS_WARN_IF(child->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(child, child->Length());
}
} else {
// If we're in a real plaintext editor, use a fast path that avoids
// calling GetChildAt() which may perform a linear search.
nsIContent* child = node->GetLastChild();
nsIContent* child = aPoint.Container()->GetLastChild();
while (child) {
if (child->IsNodeOfType(nsINode::eTEXT)) {
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
aNode = child;
aOffset = static_cast<int32_t>(aNode->Length());
if (aSelChild) {
*aSelChild = nullptr;
if (NS_WARN_IF(child->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(child, child->Length());
}
child = child->GetPreviousSibling();
}
@ -2469,27 +2451,24 @@ EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
// 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 (TextEditUtils::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());
if (aSelChild) {
*aSelChild = nullptr;
if (TextEditUtils::IsMozBR(aPoint.Container()) &&
aPoint.IsStartOfContainer()) {
nsIContent* previousSibling = aPoint.Container()->GetPreviousSibling();
if (previousSibling && previousSibling->IsNodeOfType(nsINode::eTEXT)) {
if (NS_WARN_IF(previousSibling->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(previousSibling, previousSibling->Length());
}
if (node->GetParentNode() && node->GetParentNode() == root) {
if (aSelChild) {
*aSelChild = node->AsContent();
}
aNode = node->GetParentNode();
aOffset = 0;
return;
nsINode* parentOfContainer = aPoint.Container()->GetParentNode();
if (parentOfContainer && parentOfContainer == root) {
return EditorRawDOMPoint(parentOfContainer,
aPoint.Container()->AsContent(), 0);
}
}
return aPoint;
}
nsresult
@ -2528,7 +2507,16 @@ EditorBase::InsertTextImpl(const nsAString& aStringToInsert,
// 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, address_of(child));
EditorRawDOMPoint insertionPoint;
if (child) {
insertionPoint.Set(child);
} else {
insertionPoint.Set(node, offset);
}
EditorRawDOMPoint betterPoint = FindBetterInsertionPoint(insertionPoint);
node = betterPoint.Container();
offset = betterPoint.Offset();
child = betterPoint.GetChildAtOffset();
// If a neighboring text node already exists, use that
if (!node->IsNodeOfType(nsINode::eTEXT)) {
@ -3824,20 +3812,35 @@ EditorBase::GetStartNodeAndOffset(Selection* aSelection,
*aStartContainer = nullptr;
*aStartOffset = 0;
if (!aSelection->RangeCount()) {
EditorRawDOMPoint point = EditorBase::GetStartPoint(aSelection);
if (!point.IsSet()) {
return NS_ERROR_FAILURE;
}
const nsRange* range = aSelection->GetRangeAt(0);
NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
NS_IF_ADDREF(*aStartContainer = range->GetStartContainer());
*aStartOffset = range->StartOffset();
NS_ADDREF(*aStartContainer = point.Container());
*aStartOffset = point.Offset();
return NS_OK;
}
// static
EditorRawDOMPoint
EditorBase::GetStartPoint(Selection* aSelection)
{
MOZ_ASSERT(aSelection);
if (NS_WARN_IF(!aSelection->RangeCount())) {
return EditorRawDOMPoint();
}
const nsRange* range = aSelection->GetRangeAt(0);
if (NS_WARN_IF(!range) ||
NS_WARN_IF(!range->IsPositioned())) {
return EditorRawDOMPoint();
}
return EditorRawDOMPoint(range->StartRef());
}
/**
* GetEndNodeAndOffset() returns whatever the end parent & offset is of
* the first range in the selection.
@ -4978,10 +4981,10 @@ EditorBase::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> startNode = firstRange->GetStartContainer();
int32_t startOffset = firstRange->StartOffset();
FindBetterInsertionPoint(startNode, startOffset, nullptr);
Text* textNode = startNode->GetAsText();
EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
EditorRawDOMPoint betterInsertionPoint =
FindBetterInsertionPoint(atStartOfFirstRange);
Text* textNode = betterInsertionPoint.Container()->GetAsText();
MOZ_ASSERT(textNode,
"There must be text node if mIMETextLength is larger than 0");
if (textNode) {

View File

@ -927,6 +927,7 @@ public:
static nsresult GetStartNodeAndOffset(Selection* aSelection,
nsINode** aStartContainer,
int32_t* aStartOffset);
static EditorRawDOMPoint GetStartPoint(Selection* aSelection);
static nsresult GetEndNodeAndOffset(Selection* aSelection,
nsIDOMNode** outEndNode,
int32_t* outEndOffset);
@ -1258,22 +1259,11 @@ public:
* FindBetterInsertionPoint() tries to look for better insertion point which
* is typically the nearest text node and offset in it.
*
* @param aNode in/out param, on input set to the node to use to start the search,
* on output set to the node found as the better insertion point.
* @param aOffset in/out param, on input set to the offset to use to start the
* search, on putput set to the offset found as the better insertion
* point.
* @param aSelChild in/out param, on input, can be set to nullptr if the caller
* doesn't want to pass this in, or set to a pointer to an nsCOMPtr
* pointing to the child at the input node and offset, and on output
* the method will make it point to the child at the output node and
* offset returned in aNode and aOffset.
* @param aPoint Insertion point which the callers found.
* @return Better insertion point if there is. If not returns
* same point as aPoint.
*/
void FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
int32_t& aOffset);
void FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
int32_t& aOffset,
nsCOMPtr<nsIContent>* aSelChild);
EditorRawDOMPoint FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint);
/**
* HideCaret() hides caret with nsCaret::AddForceHide() or may show carent

View File

@ -729,16 +729,16 @@ TextEditRules::WillInsertText(EditAction aAction,
// get the (collapsed) selection location
NS_ENSURE_STATE(aSelection->GetRangeAt(0));
nsCOMPtr<nsINode> selNode = aSelection->GetRangeAt(0)->GetStartContainer();
nsCOMPtr<nsIContent> selChild =
aSelection->GetRangeAt(0)->GetChildAtStartOffset();
int32_t selOffset = aSelection->GetRangeAt(0)->StartOffset();
NS_ENSURE_STATE(selNode);
EditorRawDOMPoint atStartOfSelection(aSelection->GetRangeAt(0)->StartRef());
if (NS_WARN_IF(!atStartOfSelection.IsSetAndValid())) {
return NS_ERROR_FAILURE;
}
// don't put text in places that can't have it
NS_ENSURE_STATE(mTextEditor);
if (!EditorBase::IsTextNode(selNode) &&
!mTextEditor->CanContainTag(*selNode, *nsGkAtoms::textTagName)) {
if (!EditorBase::IsTextNode(atStartOfSelection.Container()) &&
!mTextEditor->CanContainTag(*atStartOfSelection.Container(),
*nsGkAtoms::textTagName)) {
return NS_ERROR_FAILURE;
}
@ -750,22 +750,28 @@ TextEditRules::WillInsertText(EditAction aAction,
if (aAction == EditAction::insertIMEText) {
NS_ENSURE_STATE(mTextEditor);
// Find better insertion point to insert text.
mTextEditor->FindBetterInsertionPoint(selNode, selOffset,
address_of(selChild));
EditorRawDOMPoint betterInsertionPoint =
mTextEditor->FindBetterInsertionPoint(atStartOfSelection);
// If there is one or more IME selections, its minimum offset should be
// the insertion point.
int32_t IMESelectionOffset =
mTextEditor->GetIMESelectionStartOffsetIn(selNode);
mTextEditor->GetIMESelectionStartOffsetIn(
betterInsertionPoint.Container());
if (IMESelectionOffset >= 0) {
selOffset = IMESelectionOffset;
betterInsertionPoint.Set(betterInsertionPoint.Container(),
IMESelectionOffset);
}
nsCOMPtr<nsINode> selNode = betterInsertionPoint.Container();
int32_t selOffset = betterInsertionPoint.Offset();
nsCOMPtr<nsIContent> selChild = betterInsertionPoint.GetChildAtOffset();
rv = mTextEditor->InsertTextImpl(*outString, address_of(selNode),
address_of(selChild), &selOffset, doc);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// aAction == EditAction::insertText; find where we are
nsCOMPtr<nsINode> curNode = selNode;
int32_t curOffset = selOffset;
nsCOMPtr<nsINode> curNode = atStartOfSelection.Container();
int32_t curOffset = atStartOfSelection.Offset();
nsCOMPtr<nsIContent> selChild = atStartOfSelection.GetChildAtOffset();
// don't change my selection in subtransactions
NS_ENSURE_STATE(mTextEditor);

View File

@ -532,63 +532,89 @@ TextEditor::ExtendSelectionForDelete(Selection* aSelection,
GetSelectionController(getter_AddRefs(selCont));
NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE);
nsresult rv;
switch (*aAction) {
case eNextWord:
rv = selCont->WordExtendForDelete(true);
case eNextWord: {
nsresult rv = selCont->WordExtendForDelete(true);
// DeleteSelectionImpl doesn't handle these actions
// because it's inside batching, so don't confuse it:
*aAction = eNone;
break;
case ePreviousWord:
rv = selCont->WordExtendForDelete(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case ePreviousWord: {
nsresult rv = selCont->WordExtendForDelete(false);
*aAction = eNone;
break;
case eNext:
rv = selCont->CharacterExtendForDelete();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case eNext: {
nsresult rv = selCont->CharacterExtendForDelete();
// Don't set aAction to eNone (see Bug 502259)
break;
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case ePrevious: {
// Only extend the selection where the selection is after a UTF-16
// surrogate pair or a variation selector.
// For other cases we don't want to do that, in order
// to make sure that pressing backspace will only delete the last
// typed character.
nsCOMPtr<nsINode> node;
int32_t offset;
rv = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
EditorRawDOMPoint atStartOfSelection =
EditorBase::GetStartPoint(aSelection);
if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
return NS_ERROR_FAILURE;
}
// node might be anonymous DIV, so we find better text node
FindBetterInsertionPoint(node, offset, nullptr);
EditorRawDOMPoint insertionPoint =
FindBetterInsertionPoint(atStartOfSelection);
if (IsTextNode(node)) {
const nsTextFragment* data = node->GetAsText()->GetText();
if (IsTextNode(insertionPoint.Container())) {
const nsTextFragment* data =
insertionPoint.Container()->GetAsText()->GetText();
uint32_t offset = insertionPoint.Offset();
if ((offset > 1 &&
NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) &&
NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) ||
(offset > 0 &&
gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
rv = selCont->CharacterExtendForBackspace();
nsresult rv = selCont->CharacterExtendForBackspace();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
break;
return NS_OK;
}
case eToBeginningOfLine:
selCont->IntraLineMove(true, false); // try to move to end
rv = selCont->IntraLineMove(false, true); // select to beginning
case eToBeginningOfLine: {
// Try to move to end
selCont->IntraLineMove(true, false);
// Select to beginning
nsresult rv = selCont->IntraLineMove(false, true);
*aAction = eNone;
break;
case eToEndOfLine:
rv = selCont->IntraLineMove(true, true);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case eToEndOfLine: {
nsresult rv = selCont->IntraLineMove(true, true);
*aAction = eNext;
break;
default: // avoid several compiler warnings
rv = NS_OK;
break;
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// For avoiding several compiler warnings
default:
return NS_OK;
}
return rv;
}
return NS_OK;
}