mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1213589 part.5 Redesign the rules to create range in ContentEventHandler::SetRangeFromFlatTextOffset() r=smaug
This commit is contained in:
parent
a49993eb0e
commit
e894cacf0c
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user