Bug 1213589 part.5 Redesign the rules to create range in ContentEventHandler::SetRangeFromFlatTextOffset() r=smaug

This commit is contained in:
Masayuki Nakano 2015-12-02 13:20:00 +09:00
parent a49993eb0e
commit e894cacf0c
3 changed files with 526 additions and 392 deletions

View File

@ -40,6 +40,56 @@ using namespace widget;
/* ContentEventHandler */
/******************************************************************/
// NOTE
//
// ContentEventHandler *creates* ranges as following rules:
// 1. Start of range:
// 1.1. Cases: [textNode or text[Node or textNode[
// When text node is start of a range, start node is the text node and
// start offset is any number between 0 and the length of the text.
// 1.2. Case: [<element>
// When before an element node is start of a range, start node is parent
// of the element and start offset is the element's index in the parent.
// 1.3. Case: <element/>[
// When after an empty element node is start of a range, start node is
// parent of the element and start offset is the element's index in the
// parent + 1.
// 1.4. Case: <element>[
// When start of a non-empty element is start of a range, start node is
// the element and start offset is 0.
// 1.5. Case: [</root>
// When start of a range is out of bounds, start node is the root node
// and start offset is number of the children.
// 2. End of range:
// 2.1. Cases: ]textNode or text]Node or textNode]
// When a text node is end of a range, end node is the text node and
// end offset is any number between 0 and the length of the text.
// 2.2. Case: ]<element>
// When before an element node (meaning before the open tag of the
// element) is end of a range, end node is previous node causing text.
// Note that this case shouldn't be handled directly. If rule 2.1 and
// 2.3 are handled correctly, the loop with nsContentIterator shouldn't
// reach the element node since the loop should've finished already at
// handling the last node which caused some text.
// 2.3. Case: </element>]
// When after an element node is end of a range, end node is parent of
// the element node and end offset is the element's index in the parent
// + 1.
// 2.4. Case: ]</root>
// When end of a range is out of bounds, end node is the root node and
// end offset is number of the children.
//
// ContentEventHandler *treats* ranges as following additional rules:
// 1. When the start node is an element node which doesn't have children,
// it includes a line break caused before itself (i.e., includes its open
// tag). For example, if start position is { <br>, 0 }, the line break
// caused by <br> should be included into the flatten text.
// 2. When the end node is an element node which doesn't have children,
// it includes the end (i.e., includes its close tag except empty element).
// Although, currently, any close tags don't cause line break, this also
// includes its open tag. For example, if end position is { <br>, 0 }, the
// line break caused by the <br> should be included into the flatten text.
ContentEventHandler::ContentEventHandler(nsPresContext* aPresContext)
: mPresContext(aPresContext)
, mPresShell(aPresContext->GetPresShell())
@ -414,6 +464,10 @@ ContentEventHandler::GenerateFlatTextContent(nsRange* aRange,
{
NS_ASSERTION(aString.IsEmpty(), "aString must be empty string");
if (aRange->Collapsed()) {
return NS_OK;
}
nsINode* startNode = aRange->GetStartParent();
nsINode* endNode = aRange->GetEndParent();
if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
@ -580,6 +634,10 @@ ContentEventHandler::GenerateFlatFontRanges(nsRange* aRange,
{
MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array");
if (aRange->Collapsed()) {
return NS_OK;
}
nsINode* startNode = aRange->GetStartParent();
nsINode* endNode = aRange->GetEndParent();
if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
@ -696,6 +754,18 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
*aNewOffset = aOffset;
}
// Special case like <br contenteditable>
if (!mRootContent->HasChildren()) {
nsresult rv = aRange->SetStart(mRootContent, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aRange->SetEnd(mRootContent, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
nsresult rv = iter->Init(mRootContent);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -710,7 +780,8 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
if (NS_WARN_IF(!node)) {
break;
}
if (!node->IsContent()) {
// FYI: mRootContent shouldn't cause any text. So, we can skip it simply.
if (node == mRootContent || !node->IsContent()) {
continue;
}
nsIContent* content = node->AsContent();
@ -723,12 +794,14 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
continue;
}
if (offset <= aOffset && aOffset < offset + textLength) {
uint32_t xpOffset;
if (!content->IsNodeOfType(nsINode::eTEXT)) {
xpOffset = 0;
} else {
xpOffset = aOffset - offset;
// When the start offset is in between accumulated offset and the last
// offset of the node, the node is the start node of the range.
if (!startSet && aOffset <= offset + textLength) {
nsINode* startNode = nullptr;
int32_t startNodeOffset = -1;
if (content->IsNodeOfType(nsINode::eTEXT)) {
// Rule #1.1: [textNode or text[Node or textNode[
uint32_t xpOffset = aOffset - offset;
if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
xpOffset = ConvertToXPOffset(content, xpOffset);
}
@ -744,27 +817,61 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
*aNewOffset -= (oldXPOffset - xpOffset);
}
}
startNode = content;
startNodeOffset = static_cast<int32_t>(xpOffset);
} else if (aOffset < offset + textLength) {
// Rule #1.2 [<element>
startNode = content->GetParent();
if (NS_WARN_IF(!startNode)) {
return NS_ERROR_FAILURE;
}
startNodeOffset = startNode->IndexOf(content);
if (NS_WARN_IF(startNodeOffset == -1)) {
// The content is being removed from the parent!
return NS_ERROR_FAILURE;
}
} else if (!content->HasChildren()) {
// Rule #1.3: <element/>[
startNode = content->GetParent();
if (NS_WARN_IF(!startNode)) {
return NS_ERROR_FAILURE;
}
startNodeOffset = startNode->IndexOf(content) + 1;
if (NS_WARN_IF(startNodeOffset == 0)) {
// The content is being removed from the parent!
return NS_ERROR_FAILURE;
}
} else {
// Rule #1.4: <element>[
startNode = content;
startNodeOffset = 0;
}
rv = aRange->SetStart(content, int32_t(xpOffset));
NS_ASSERTION(startNode, "startNode must not be nullptr");
NS_ASSERTION(startNodeOffset >= 0,
"startNodeOffset must not be negative");
rv = aRange->SetStart(startNode, startNodeOffset);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
startSet = true;
if (aLength == 0) {
// Ensure that the end offset and the start offset are same.
rv = aRange->SetEnd(content, int32_t(xpOffset));
if (!aLength) {
rv = aRange->SetEnd(startNode, startNodeOffset);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
}
// When the end offset is in the content, the node is the end node of the
// range.
if (endOffset <= offset + textLength) {
nsINode* endNode = content;
uint32_t xpOffset;
MOZ_ASSERT(startSet,
"The start of the range should've been set already");
if (content->IsNodeOfType(nsINode::eTEXT)) {
xpOffset = endOffset - offset;
// Rule #2.1: ]textNode or text]Node or textNode]
uint32_t xpOffset = endOffset - offset;
if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
xpOffset = ConvertToXPOffset(content, xpOffset);
}
@ -774,18 +881,36 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
return rv;
}
}
} else {
// Use first position of next node, because the end node is ignored
// by ContentIterator when the offset is zero.
xpOffset = 0;
iter->Next();
if (iter->IsDone()) {
break;
NS_ASSERTION(xpOffset <= INT32_MAX,
"The end node offset is too large");
rv = aRange->SetEnd(content, static_cast<int32_t>(xpOffset));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
endNode = iter->GetCurrentNode();
return NS_OK;
}
rv = aRange->SetEnd(endNode, int32_t(xpOffset));
if (endOffset == offset) {
// Rule #2.2: ]<element>
// NOTE: Please don't crash on release builds because it must be
// overreaction but we shouldn't allow this bug when some
// automated tests find this.
MOZ_ASSERT(false, "This case should've already been handled at "
"the last node which caused some text");
return NS_ERROR_FAILURE;
}
// Rule #2.3: </element>]
nsINode* endNode = content->GetParent();
if (NS_WARN_IF(!endNode)) {
return NS_ERROR_FAILURE;
}
int32_t indexInParent = endNode->IndexOf(content);
if (NS_WARN_IF(indexInParent == -1)) {
// The content is being removed from the parent!
return NS_ERROR_FAILURE;
}
rv = aRange->SetEnd(endNode, indexInParent + 1);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -795,13 +920,11 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
offset += textLength;
}
if (offset < aOffset) {
return NS_ERROR_FAILURE;
}
if (!startSet) {
// Rule #1.5: [</root>
MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT));
rv = aRange->SetStart(mRootContent, int32_t(mRootContent->GetChildCount()));
rv = aRange->SetStart(mRootContent,
static_cast<int32_t>(mRootContent->GetChildCount()));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -809,7 +932,9 @@ ContentEventHandler::SetRangeFromFlatTextOffset(nsRange* aRange,
*aNewOffset = offset;
}
}
rv = aRange->SetEnd(mRootContent, int32_t(mRootContent->GetChildCount()));
// Rule #2.4: ]</root>
rv = aRange->SetEnd(mRootContent,
static_cast<int32_t>(mRootContent->GetChildCount()));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -1450,6 +1575,11 @@ ContentEventHandler::GetFlatTextLengthInRange(
return NS_ERROR_INVALID_ARG;
}
if (aStartPosition == aEndPosition) {
*aLength = 0;
return NS_OK;
}
// Don't create nsContentIterator instance until it's really necessary since
// destroying without initializing causes unexpected NS_ASSERTION() call.
nsCOMPtr<nsIContentIterator> iter;
@ -1655,33 +1785,36 @@ static void AdjustRangeForSelection(nsIContent* aRoot,
{
nsINode* node = *aNode;
int32_t nodeOffset = *aNodeOffset;
if (aRoot != node && node->GetParent()) {
if (node->IsNodeOfType(nsINode::eTEXT)) {
// When the offset is at the end of the text node, set it to after the
// text node, to make sure the caret is drawn on a new line when the last
// character of the text node is '\n'
int32_t nodeLength =
static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength());
MOZ_ASSERT(nodeOffset <= nodeLength, "Offset is past length of text node");
if (nodeOffset == nodeLength) {
node = node->GetParent();
nodeOffset = node->IndexOf(*aNode) + 1;
}
} else {
node = node->GetParent();
nodeOffset = node->IndexOf(*aNode) + (nodeOffset ? 1 : 0);
}
if (aRoot == node || NS_WARN_IF(!node->GetParent()) ||
!node->IsNodeOfType(nsINode::eTEXT)) {
return;
}
nsIContent* brContent = node->GetChildAt(nodeOffset - 1);
while (brContent && brContent->IsHTMLElement()) {
if (!brContent->IsHTMLElement(nsGkAtoms::br) || IsContentBR(brContent)) {
break;
}
brContent = node->GetChildAt(--nodeOffset - 1);
// When the offset is at the end of the text node, set it to after the
// text node, to make sure the caret is drawn on a new line when the last
// character of the text node is '\n' in <textarea>.
int32_t textLength =
static_cast<int32_t>(static_cast<nsIContent*>(node)->TextLength());
MOZ_ASSERT(nodeOffset <= textLength, "Offset is past length of text node");
if (nodeOffset != textLength) {
return;
}
*aNode = node;
*aNodeOffset = std::max(nodeOffset, 0);
nsIContent* aRootParent = aRoot->GetParent();
if (NS_WARN_IF(!aRootParent)) {
return;
}
// If the root node is not an anonymous div of <textarea>, we don't need to
// do this hack. If you did this, ContentEventHandler couldn't distinguish
// if the range includes open tag of the next node in some cases, e.g.,
// textNode]<p></p> vs. textNode<p>]</p>
if (!aRootParent->IsHTMLElement(nsGkAtoms::textarea)) {
return;
}
*aNode = node->GetParent();
MOZ_ASSERT((*aNode)->IndexOf(node) != -1);
*aNodeOffset = (*aNode)->IndexOf(node) + 1;
}
nsresult

View File

@ -107,6 +107,11 @@ public:
{
}
bool operator==(const NodePosition& aOther) const
{
return mNode == aOther.mNode && mOffset == aOther.mOffset;
}
bool IsValid() const
{
return mNode && mOffset >= 0;

File diff suppressed because it is too large Load Diff