/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include"TextDirectiveUtil.h" #include"nsComputedDOMStyle.h" #include"nsDOMAttributeMap.h" #include"nsFind.h" #include"nsFrameSelection.h" #include"nsGkAtoms.h" #include"nsIFrame.h" #include"nsINode.h" #include"nsIURI.h" #include"nsRange.h" #include"nsString.h" #include"nsTArray.h" #include"nsUnicharUtils.h" #include"ContentIterator.h" #include"Document.h" #include"fragmentdirectives_ffi_generated.h" #include"Text.h" #include"mozilla/ContentIterator.h" #include"mozilla/ResultVariant.h" #include"mozilla/intl/WordBreaker.h" #include"mozilla/SelectionMovementUtils.h"
/* static */ bool TextDirectiveUtil::NodeIsSearchInvisible(nsINode& aNode) { if (!aNode.IsElement()) { returnfalse;
} // 2. If the node serializes as void.
nsAtom* nodeNameAtom = aNode.NodeInfo()->NameAtom(); if (FragmentOrElement::IsHTMLVoid(nodeNameAtom)) { returntrue;
} // 3. Is any of the following types: HTMLIFrameElement, HTMLImageElement, // HTMLMeterElement, HTMLObjectElement, HTMLProgressElement, HTMLStyleElement, // HTMLScriptElement, HTMLVideoElement, HTMLAudioElement if (aNode.IsAnyOfHTMLElements(
nsGkAtoms::iframe, nsGkAtoms::image, nsGkAtoms::meter,
nsGkAtoms::object, nsGkAtoms::progress, nsGkAtoms::style,
nsGkAtoms::script, nsGkAtoms::video, nsGkAtoms::audio)) { returntrue;
} // 4. Is a select element whose multiple content attribute is absent. if (aNode.IsHTMLElement(nsGkAtoms::select)) { return aNode.GetAttributes()->GetNamedItem(u"multiple"_ns) == nullptr;
} // This is tested last because it's the most expensive check. // 1. The computed value of its 'display' property is 'none'. const Element* nodeAsElement = Element::FromNode(aNode); const RefPtr<const ComputedStyle> computedStyle =
nsComputedDOMStyle::GetComputedStyleNoFlush(nodeAsElement); return !computedStyle ||
computedStyle->StyleDisplay()->mDisplay == StyleDisplay::None;
}
/* static */ nsINode* TextDirectiveUtil::GetBlockAncestorForNode(
nsINode* aNode) { // 1. Let curNode be node.
RefPtr<nsINode> curNode = aNode; // 2. While curNode is non-null while (curNode) { // 2.1. If curNode is not a Text node and it has block-level display then // return curNode. if (!curNode->IsText() && NodeHasBlockLevelDisplay(*curNode)) { return curNode;
} // 2.2. Otherwise, set curNode to curNode’s parent.
curNode = curNode->GetParentNode();
} // 3.Return node’s node document's document element. return aNode->GetOwnerDocument();
}
/* static */ bool TextDirectiveUtil::NodeIsPartOfNonSearchableSubTree(
nsINode& aNode) {
nsINode* node = &aNode; do { if (NodeIsSearchInvisible(*node)) { returntrue;
}
} while ((node = node->GetParentOrShadowHostNode())); returnfalse;
}
/* static */ RangeBoundary TextDirectiveUtil::GetBoundaryPointAtIndex(
uint32_t aIndex, const nsTArray<RefPtr<Text>>& aTextNodeList,
IsEndIndex aIsEndIndex) { // 1. Let counted be 0.
uint32_t counted = 0; // 2. For each curNode of nodes: for (Text* curNode : aTextNodeList) { // 2.1. Let nodeEnd be counted + curNode’s length.
uint32_t nodeEnd = counted + curNode->Length(); // 2.2. If isEnd is true, add 1 to nodeEnd. if (aIsEndIndex == IsEndIndex::Yes) {
++nodeEnd;
} // 2.3. If nodeEnd is greater than index then: if (nodeEnd > aIndex) { // 2.3.1. Return the boundary point (curNode, index − counted). return RangeBoundary(curNode->AsNode(), aIndex - counted);
} // 2.4. Increment counted by curNode’s length.
counted += curNode->Length();
} return {};
}
/* static */ void TextDirectiveUtil::AdvanceStartToNextNonWhitespacePosition(
nsRange& aRange) { // 1. While range is not collapsed: while (!aRange.Collapsed()) { // 1.1. Let node be range's start node.
RefPtr<nsINode> node = aRange.GetStartContainer();
MOZ_ASSERT(node); // 1.2. Let offset be range's start offset. const uint32_t offset = aRange.StartOffset(); // 1.3. If node is part of a non-searchable subtree or if node is not a // visible text node or if offset is equal to node's length then: if (NodeIsPartOfNonSearchableSubTree(*node) ||
!NodeIsVisibleTextNode(*node) || offset == node->Length()) { // 1.3.1. Set range's start node to the next node, in shadow-including // tree order. // 1.3.2. Set range's start offset to 0. if (NS_FAILED(aRange.SetStart(node->GetNextNode(), 0))) { return;
} // 1.3.3. Continue. continue;
} const Text* text = Text::FromNode(node);
MOZ_ASSERT(text); // These steps are moved to `IsWhitespaceAtPosition()`. // 1.4. If the substring data of node at offset offset and count 6 is equal // to the string " " then: // 1.4.1. Add 6 to range’s start offset. // 1.5. Otherwise, if the substring data of node at offset offset and count // 5 is equal to the string " " then: // 1.5.1. Add 5 to range’s start offset. // 1.6. Otherwise: // 1.6.1 Let cp be the code point at the offset index in node’s data. // 1.6.2 If cp does not have the White_Space property set, return. // 1.6.3 Add 1 to range’s start offset. if (!IsWhitespaceAtPosition(text, offset)) { return;
}
aRange.SetStart(node, offset + 1);
}
}
/* static */ RangeBoundary
TextDirectiveUtil::MoveBoundaryToNextNonWhitespacePosition( const RangeBoundary& aRangeBoundary) {
MOZ_ASSERT(aRangeBoundary.IsSetAndValid());
nsINode* node = aRangeBoundary.Container();
uint32_t offset =
*aRangeBoundary.Offset(RangeBoundary::OffsetFilter::kValidOffsets); while (node) { if (TextDirectiveUtil::NodeIsPartOfNonSearchableSubTree(*node) ||
!TextDirectiveUtil::NodeIsVisibleTextNode(*node) ||
offset == node->Length()) {
nsINode* newNode = node->GetNextNode(); if (!newNode) { // jjaschke: I don't see a situation where this could happen. However, // let's return the original range boundary as fallback. return aRangeBoundary;
}
node = newNode;
offset = 0; continue;
} const Text* text = Text::FromNode(node);
MOZ_ASSERT(text); if (TextDirectiveUtil::IsWhitespaceAtPosition(text, offset)) {
++offset; continue;
} return {node, offset};
}
MOZ_ASSERT_UNREACHABLE("All code paths must return in the loop."); return {};
}
/* static */ RangeBoundary
TextDirectiveUtil::MoveBoundaryToPreviousNonWhitespacePosition( const RangeBoundary& aRangeBoundary) {
MOZ_ASSERT(aRangeBoundary.IsSetAndValid());
nsINode* node = aRangeBoundary.Container();
uint32_t offset =
*aRangeBoundary.Offset(RangeBoundary::OffsetFilter::kValidOffsets); // Decrement offset by one so that the actual previous character is used. This // means that we need to increment the offset by 1 when we have found the // non-whitespace character. while (node) { if (TextDirectiveUtil::NodeIsPartOfNonSearchableSubTree(*node) ||
!TextDirectiveUtil::NodeIsVisibleTextNode(*node) || offset == 0) {
nsIContent* newNode = node->GetPrevNode(); if (!newNode) { // jjaschke: I don't see a situation where this could happen. However, // let's return the original range boundary as fallback. return aRangeBoundary;
}
node = newNode;
offset = node->Length(); continue;
} const Text* text = Text::FromNode(node);
MOZ_ASSERT(text); if (TextDirectiveUtil::IsWhitespaceAtPosition(text, offset - 1)) {
--offset; continue;
} return {node, offset};
}
MOZ_ASSERT_UNREACHABLE("All code paths must return in the loop."); return {};
}
/* static */ Result<RangeBoundary, ErrorResult>
TextDirectiveUtil::FindNextBlockBoundary(const RangeBoundary& aRangeBoundary,
TextScanDirection aDirection) {
MOZ_ASSERT(aRangeBoundary.IsSetAndValid()); auto findNextBlockBoundaryInternal =
[aDirection](const RangeBoundary& rangeBoundary)
-> Result<RangeBoundary, ErrorResult> {
PeekOffsetOptions options = {
PeekOffsetOption::JumpLines, PeekOffsetOption::StopAtScroller,
PeekOffsetOption::IsKeyboardSelect, PeekOffsetOption::Extend}; return SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
rangeBoundary,
aDirection == TextScanDirection::Left ? nsDirection::eDirPrevious
: nsDirection::eDirNext,
CaretAssociationHint::After,
intl::BidiEmbeddingLevel::DefaultLTR(),
nsSelectionAmount::eSelectParagraph, options)
.mapErr([](nsresult rv) { return ErrorResult(rv); });
}; auto maybeNewBoundary = findNextBlockBoundaryInternal(aRangeBoundary); if (MOZ_UNLIKELY(maybeNewBoundary.isErr())) { return maybeNewBoundary.propagateErr();
} auto newBoundary = maybeNewBoundary.unwrap(); while (NormalizedRangeBoundariesAreEqual(aRangeBoundary, newBoundary)) {
maybeNewBoundary = findNextBlockBoundaryInternal(newBoundary); if (MOZ_UNLIKELY(maybeNewBoundary.isErr())) { return maybeNewBoundary.propagateErr();
} if (maybeNewBoundary.inspect() == newBoundary) { return newBoundary; // we reached the end or so?
}
newBoundary = maybeNewBoundary.unwrap();
} return newBoundary;
}
Maybe<int32_t> compare =
nsContentUtils::ComparePoints(boundary, aRange.EndRef()); if (!compare || *compare != -1) { // *compare == -1 means that the found block boundary is after the range // end, and therefore outside of the range. return Result<Maybe<RangeBoundary>, ErrorResult>(Nothing{});
}
return Some(boundary);
}
Result<RangeBoundary, ErrorResult> maybeBoundary =
FindNextBlockBoundary(aRange.EndRef(), TextScanDirection::Left); if (MOZ_UNLIKELY(maybeBoundary.isErr())) { return maybeBoundary.propagateErr();
}
RangeBoundary boundary = maybeBoundary.unwrap(); auto compare = nsContentUtils::ComparePoints(aRange.StartRef(), boundary); if (!compare || *compare != -1) { // *compare == 1 means that the found block boundary is before the range // start boundary, and therefore outside of the range. return Result<Maybe<RangeBoundary>, ErrorResult>(Nothing{});
} return Some(boundary);
}
for (uint32_t i = startIndex; i < endIndex; ++i) {
char16_t ch = textFragment.CharAt(i); if (!nsContentUtils::IsHTMLWhitespaceOrNBSP(ch)) { returnfalse;
}
} returntrue;
}; const nsINode* node1 = aRangeBoundary1.Container(); const nsINode* node2 = aRangeBoundary2.Container();
size_t offset1 =
*aRangeBoundary1.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
size_t offset2 =
*aRangeBoundary2.Offset(RangeBoundary::OffsetFilter::kValidOffsets);
if (node1 == node2) { if (const Text* text = Text::FromNodeOrNull(node1)) { return textSubStringIsOnlyWhitespace(text, offset1, offset2);
} return offset1 == offset2;
}
mozilla::UnsafePreContentIterator iter; // ContentIterator classes require boundaries to be in correct order. auto comp = nsContentUtils::ComparePoints(aRangeBoundary1, aRangeBoundary2); if (!comp) { returnfalse;
} if (*comp == 0) { returntrue;
} constauto& [firstBoundary, secondBoundary] =
*comp == -1 ? std::tuple{&aRangeBoundary1, &aRangeBoundary2}
: std::tuple{&aRangeBoundary2, &aRangeBoundary1};
if (NS_FAILED(iter.Init(firstBoundary->AsRaw(), secondBoundary->AsRaw()))) { returnfalse;
}
for (; !iter.IsDone(); iter.Next()) { auto* node = iter.GetCurrentNode(); if (!node || !TextDirectiveUtil::NodeIsVisibleTextNode(*node)) { continue;
} if (node == firstBoundary->Container()) { auto firstOffset =
*firstBoundary->Offset(RangeBoundary::OffsetFilter::kValidOffsets); if (firstOffset == node->Length()) { // if this is the start node, it's a text node and the offset is at the // end, continue with the next node. continue;
} if (const Text* text = Text::FromNodeOrNull(node)) { if (textSubStringIsOnlyWhitespace(text, firstOffset, text->Length())) { continue;
}
}
} if (node == secondBoundary->Container()) { auto secondOffset =
*secondBoundary->Offset(RangeBoundary::OffsetFilter::kValidOffsets); if (secondOffset == 0) { // if this is the end node, it's a text node and the offset is 0, return // true. returntrue;
} if (const Text* text = Text::FromNodeOrNull(node)) { if (textSubStringIsOnlyWhitespace(text, 0, secondOffset)) { returntrue;
}
}
}
if (node->Length() && !node->AsText()->TextIsOnlyWhitespace()) { // if the text node only contains whitespace, ignore it; otherwise, the // boundaries are not at the same spot. returnfalse;
}
} returntrue;
}
/* static */ Result<Ok, ErrorResult>
TextDirectiveUtil::ExtendRangeToWordBoundaries(nsRange& aRange) {
MOZ_ASSERT(!aRange.Collapsed());
PeekOffsetOptions options = {
PeekOffsetOption::JumpLines, PeekOffsetOption::StopAtScroller,
PeekOffsetOption::IsKeyboardSelect, PeekOffsetOption::Extend}; // To extend a range `inputRange` to its word boundaries, perform these steps: // 1. To extend the start boundary: // 1.1 Let `newStartBoundary` be a range boundary, initially null. // 1.2 Create a new range boundary `rangeStartWordEndBoundary` at the next // word end boundary at `inputRange`s start point. // 1.3 Then, create a new range boundary `rangeStartWordStartBoundary` // at the previous word start boundary of `rangeStartWordEndBoundary` // 1.4 If `rangeStartWordStartBoundary` is not at the same (normalized) // position as `inputRange`s start point, let `newStartBoundary` be // `rangeStartWordStartBoundary`.
Result<Maybe<RangeBoundary>, ErrorResult> newStartBoundary =
SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
aRange.StartRef(), nsDirection::eDirNext, CaretAssociationHint::After,
intl::BidiEmbeddingLevel::DefaultLTR(),
nsSelectionAmount::eSelectWord, options)
.andThen([&options](const RangeBoundary& rangeStartWordEndBoundary) { return SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
rangeStartWordEndBoundary, nsDirection::eDirPrevious,
CaretAssociationHint::Before,
intl::BidiEmbeddingLevel::DefaultLTR(),
nsSelectionAmount::eSelectWord, options);
})
.map([&rangeStart = aRange.StartRef()](
RangeBoundary&& rangeStartWordStartBoundary) { return NormalizedRangeBoundariesAreEqual(
rangeStartWordStartBoundary, rangeStart)
? Nothing{}
: Some(std::move(rangeStartWordStartBoundary));
})
.mapErr([](nsresult rv) { return ErrorResult(rv); }); if (MOZ_UNLIKELY(newStartBoundary.isErr())) { return newStartBoundary.propagateErr();
}
// 2. To extend the end boundary: // 2.1 Let `newEndBoundary` be a range boundary, initially null. // 2.2 Create a new range boundary `rangeEndWordStartBoundary` at the previous // word start boundary at `inputRange`s end point. // 2.3 Then, create a new range boundary `rangeEndWordEndBoundary` at the next // word end boundary from `rangeEndWordStartBoundary`. // 2.4 If `rangeEndWordEndBoundary` is not at the same (normalized) position // as `inputRange`s end point, let `newEndBoundary` be // `rangEndWordEndBoundary`.
Result<Maybe<RangeBoundary>, ErrorResult> newEndBoundary =
SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
aRange.EndRef(), nsDirection::eDirPrevious,
CaretAssociationHint::Before, intl::BidiEmbeddingLevel::DefaultLTR(),
nsSelectionAmount::eSelectWord, options)
.andThen([&options](const RangeBoundary& rangeEndWordStartBoundary) { return SelectionMovementUtils::MoveRangeBoundaryToSomewhere(
rangeEndWordStartBoundary, nsDirection::eDirNext,
CaretAssociationHint::After,
intl::BidiEmbeddingLevel::DefaultLTR(),
nsSelectionAmount::eSelectWord, options);
})
.map([&rangeEnd =
aRange.EndRef()](RangeBoundary&& rangeEndWordEndBoundary) { return NormalizedRangeBoundariesAreEqual(rangeEndWordEndBoundary,
rangeEnd)
? Nothing{}
: Some(std::move(rangeEndWordEndBoundary));
})
.mapErr([](auto rv) { return ErrorResult(rv); }); if (MOZ_UNLIKELY(newEndBoundary.isErr())) { return newEndBoundary.propagateErr();
} // 3. If `newStartBoundary` is not null, set `inputRange`s start point to // `newStartBoundary`.
MOZ_TRY(newStartBoundary.andThen(
[&aRange](Maybe<RangeBoundary>&& boundary) -> Result<Ok, ErrorResult> { if (boundary.isNothing() || !boundary->IsSetAndValid()) { return Ok();
}
ErrorResult rv;
aRange.SetStart(boundary->AsRaw(), rv); if (MOZ_UNLIKELY(rv.Failed())) { return Err(std::move(rv));
} return Ok();
})); // 4. If `newEndBoundary` is not null, set `inputRange`s end point to // `newEndBoundary`.
MOZ_TRY(newEndBoundary.andThen(
[&aRange](Maybe<RangeBoundary>&& boundary) -> Result<Ok, ErrorResult> { if (boundary.isNothing() || !boundary->IsSetAndValid()) { return Ok();
}
ErrorResult rv;
aRange.SetEnd(boundary->AsRaw(), rv); if (MOZ_UNLIKELY(rv.Failed())) { return Err(std::move(rv));
} return Ok();
}));
while (commonLength < maxCommonLength) { if (*iter1 != *iter2) { break;
}
++iter1;
++iter2;
++commonLength;
} // this condition ensures that if the high surrogate is removed if the low // surrogate does not match. if (commonLength && NS_IS_HIGH_SURROGATE(*(iter1 - 1))) {
--commonLength;
} return commonLength;
}
uint32_t TextDirectiveUtil::FindCommonSuffix(const nsAString& aFoldedStr1, const nsAString& aFoldedStr2) { const uint32_t maxCommonLength =
std::min(aFoldedStr1.Length(), aFoldedStr2.Length());
uint32_t commonLength = 0; const char16_t* iter1 = aFoldedStr1.EndReading(); const char16_t* iter2 = aFoldedStr2.EndReading(); while (commonLength != maxCommonLength) { if (*(iter1 - 1) != *(iter2 - 1)) { break;
}
--iter1;
--iter2;
++commonLength;
} // this condition ensures that a matching low surrogate is removed if the high // surrogate does not match. if (commonLength && NS_IS_LOW_SURROGATE(*iter1)) {
--commonLength;
} return commonLength;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.