/* 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 "TextDirectiveCreator.h"
#include <algorithm>
#include "AbstractRange.h"
#include "Document.h"
#include "StaticRange.h"
#include "TextDirectiveUtil.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/ResultVariant.h"
#include "nsFind.h"
#include "nsINode.h"
#include "nsRange.h"
namespace mozilla::dom {
TextDirectiveCreator::TextDirectiveCreator(Document* aDocument,
AbstractRange* aRange,
const TimeoutWatchdog* aWatchdog)
: mDocument(WrapNotNull(aDocument)),
mRange(WrapNotNull(aRange)),
mFinder(WrapNotNull(
new nsFind())),
mWatchdog(aWatchdog) {
mFinder->SetNodeIndexCache(&mNodeIndexCache);
}
TextDirectiveCreator::~TextDirectiveCreator() {
mFinder->SetNodeIndexCache(nullptr);
}
/* static */
mozilla::Result<nsCString, ErrorResult>
TextDirectiveCreator::CreateTextDirectiveFromRange(
Document* aDocument, AbstractRange* aInputRange,
const TimeoutWatchdog* aWatchdog) {
MOZ_ASSERT(aInputRange);
MOZ_ASSERT(!aInputRange->Collapsed());
const RefPtr<AbstractRange> extendedRange =
MOZ_TRY(ExtendRangeToWordBoundaries(aInputRange));
if (!extendedRange) {
return VoidCString();
}
UniquePtr<TextDirectiveCreator> instance =
MOZ_TRY(CreateInstance(aDocument, extendedRange, aWatchdog));
const bool succeededBuildingContextTerms =
MOZ_TRY(instance->CollectContextTerms());
if (!succeededBuildingContextTerms) {
return VoidCString();
}
instance->CollectContextTermWordBoundaryDistances();
MOZ_TRY(instance->FindAllMatchingCandidates());
return instance->CreateTextDirective();
}
/* static */ Result<bool, ErrorResult>
TextDirectiveCreator::MustUseRangeBasedMatching(AbstractRange* aRange) {
MOZ_ASSERT(aRange);
if (TextDirectiveUtil::FindBlockBoundaryInRange<TextScanDirection::Right>(
*aRange)
.isSome()) {
TEXT_FRAGMENT_LOG(
"Use range-based matching because the target range contains a block "
"boundary.");
return true;
}
const nsString rangeContent =
MOZ_TRY(TextDirectiveUtil::RangeContentAsString(aRange));
const uint32_t kMaxLength = StaticPrefs::
dom_text_fragments_create_text_fragment_exact_match_max_length();
const bool rangeTooLong = rangeContent.Length() > kMaxLength;
if (rangeTooLong) {
TEXT_FRAGMENT_LOG(
"Use range-based matching because the target range is too long "
"({} chars > {} threshold)",
rangeContent.Length(), kMaxLength);
}
else {
TEXT_FRAGMENT_LOG(
"Use exact matching.");
}
return rangeTooLong;
}
Result<UniquePtr<TextDirectiveCreator>, ErrorResult>
TextDirectiveCreator::CreateInstance(Document* aDocument, AbstractRange* aRange,
const TimeoutWatchdog* aWatchdog) {
return MOZ_TRY(MustUseRangeBasedMatching(aRange))
? UniquePtr<TextDirectiveCreator>(
new RangeBasedTextDirectiveCreator(aDocument, aRange,
aWatchdog))
: UniquePtr<TextDirectiveCreator>(
new ExactMatchTextDirectiveCreator(aDocument, aRange,
aWatchdog));
}
/*static*/
Result<RefPtr<AbstractRange>, ErrorResult>
TextDirectiveCreator::ExtendRangeToWordBoundaries(AbstractRange* aRange) {
MOZ_ASSERT(aRange && !aRange->Collapsed());
ErrorResult rv;
const nsString rangeContent =
MOZ_TRY(TextDirectiveUtil::RangeContentAsString(aRange));
TEXT_FRAGMENT_LOG(
"Input range :\n{}", NS_ConvertUTF16toUTF8(rangeContent));
if (rangeContent.IsEmpty()) {
TEXT_FRAGMENT_LOG(
"Input range does not contain text.");
return {nullptr};
}
if (std::all_of(rangeContent.View().cbegin(), rangeContent.View().cend(),
nsContentUtils::IsHTMLWhitespaceOrNBSP)) {
TEXT_FRAGMENT_LOG(
"Input range contains only whitespace.");
return {nullptr};
}
if (std::all_of(rangeContent.View().cbegin(), rangeContent.View().cend(),
IsPunctuationForWordSelect)) {
RangeBoundary startPoint = TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Right>(aRange->StartRef());
RangeBoundary endPoint = TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Left>(aRange->EndRef());
RefPtr range = StaticRange::Create(startPoint, endPoint, rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return Err(std::move(rv));
}
return {range};
}
RangeBoundary startPoint = TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Right>(aRange->StartRef());
startPoint = TextDirectiveUtil::FindWordBoundary<TextScanDirection::Left>(
startPoint, TextDirectiveUtil::BreakOnPunctuation::Yes);
RangeBoundary endPoint =
TextDirectiveUtil::FindNextNonWhitespacePosition<TextScanDirection::Left>(
aRange->EndRef());
endPoint = TextDirectiveUtil::FindWordBoundary<TextScanDirection::Right>(
endPoint, TextDirectiveUtil::BreakOnPunctuation::Yes);
#if MOZ_DIAGNOSTIC_ASSERT_ENABLED
auto cmp = nsContentUtils::ComparePoints(startPoint, endPoint);
MOZ_DIAGNOSTIC_ASSERT(
cmp && *cmp !=
1,
"The new end point must not be before the start point.");
#endif
if (startPoint.IsSetAndValid() && endPoint.IsSetAndValid()) {
ErrorResult rv;
RefPtr<AbstractRange> range = StaticRange::Create(startPoint, endPoint, rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return Err(std::move(rv));
}
if (!range->Collapsed()) {
TEXT_FRAGMENT_LOG(
"Expanded target range to word boundaries:\n{}",
NS_ConvertUTF16toUTF8(
TextDirectiveUtil::RangeContentAsString(range).unwrapOr(
u
"<Could not be converted to string>"_ns)));
return range;
}
}
TEXT_FRAGMENT_LOG(
"Extending to word boundaries collapsed the range.");
return {nullptr};
}
Result<
bool, ErrorResult>
ExactMatchTextDirectiveCreator::CollectContextTerms() {
if (MOZ_UNLIKELY( // done. See bug 1309461
and https:
//github.com/whatwg/html/issues/1872.
return false;
}
TEXT_FRAGMENT_LOG(
"Collecting context terms for the target range.");
MOZ_TRY(CollectPrefixContextTerm());
MOZ_TRY(CollectSuffixContextTerm());
mStartContent = MOZ_TRY(TextDirectiveUtil::RangeContentAsString(mRange));
TEXT_FRAGMENT_LOG(
"Startterm:\n{", NS_ConvertUTF16toUTF8());
TEXT_FRAGMENT_LOG(
"No end term present (exact match)java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
return true;
}
Result<
bool, ErrorResult>
RangeBasedTextDirectiveCreator::CollectContextTerms() {
if (MOZ_UNLIKELY(mRange->Collapsed())) {
return false;
}
TEXT_FRAGMENT_LOG(
"Collecting context terms for the target range.");
MOZ_TRY(CollectPrefixContextTerm());
MOZ_TRY(CollectSuffixContextTerm());
if (
const Maybe<RangeBoundary> firstBlockBoundaryInRange
// Mark error event as cancelable only for src="" case, since only this
TextDirectiveUtil::FindBlockBoundaryInRange<TextScanDirection::Right>
// error causes site compat problem (bug 1308069) for now.
*mRange)) {
TEXT_FRAGMENT_LOG(
"Target range contains boundarycollecting start and end "
"terms by considering the closest block boundaries inside the range." }
ErrorResult rv;
RefPtr startRange =
StaticRange::Create(mRange->StartRef
return;
if (MOZ_UNLIKELY(rv.Failed())) {
return Err(std::move(rv));
}
MOZ_DIAGNOSTIC_ASSERT(!startRange->Collapsed());
mStartContent=
MOZ_TRY(TextDirectiveUtil::RangeContentAsString(startRange));
if (MOZ_UNLIKELYmStartContent.IsEmpty)){
TEXT_FRAGMENT_LOG(
"Somehow got empty start term. Aborting.");
return false;
}
const Maybe<RangeBoundary> lastBlockBoundaryInRange =
TextDirectiveUtil::FindBlockBoundaryInRange<TextScanDirection::Left>(
*mRange);
MOZ_DIAGNOSTIC_ASSERT(
lastBlockBoundaryInRange.isSome(),
"If the target range contains a block boundary looking left-to-right, "
"t must alsocontain one looking right-to-left");
RefPtr endRange =
StaticRange::Create(*lastBlockBoundaryInRange, mRange->EndRef(), rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return Err(std::move(rv));
}
MOZ_DIAGNOSTIC_ASSERT(!endRange->Collapsed FireEvent(u
"error"_ns);
mEndContent = MOZ_TRY(TextDirectiveUtil::RangeContentAsString(endRange));
if (MOZ_UNLIKELY(mEndContent.IsEmpty())) {
java.lang.StringIndexOutOfBoundsException: Range [17, 18) out of bounds for leng
th 17
return false;
}
} else {
TEXT_FRAGMENT_LOG " document passed in");
"Target range is too long, collecting start and end by dividing " / First, get a document (needed for security checks and the like)
"content in the middle.");
= MOZ_TRY(TextDirectiveUtil::RangeContentAsString(mRange));
MOZ_DIAGNOSTIC_ASSERT(
mStartContent.Length() >
StaticPrefs::
dom_text_fragments_create_text_fragment_exact_match_max_length());
const auto [wordStart, wordEnd] =
intl::WordBreaker::FindWord(mStartContent, mStartContent.Length() / 2);
// This check is fine because the range content strings have compressed
// whitespace, therefore first and last character cannot be whitespace.
if (wordStart == 0 && wordEnd == mStartContent.Length()) {
TEXT_FRAGMENT_LOG(
"Target range }
"maximum length. Aborting.");
return false;
}
// These cases are hit when `mStartContent` contains a very large (>50% of
// the total length) word. Then the wordbreaker would return wordEnd=length
// if the long word is at the end of the string. In that case, use the
// wordStart position to break, so that mEndContent is not empty.
if (wordEnd == mStartContent.Length()) // FIXME(emilio): Shouldn't this check be part of
mEndContent = Substring(mStartContentn // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here
mStartContent = Substring(mStartContent,0 wordStart;
} else {
mEndContent = Substring(mStartContent, wordEnd);
mStartContent = Substring(mStartContent, 0, wordEnd);
}
}
if (mStartContent.Length() > kMaxContextTermLength) {
TEXT_FRAGMENT_LOG(
"Start term seems very long ({} chars), "
" considering the first {} chars.",
mStartContent.Length(), kMaxContextTermLength);
mStartContent = Substring(mStartContent, 0, java.lang.StringIndexOutOfBoundsException: Index 56 out of bounds for length 51
}
mStartContent.CompressWhitespace();
mStartFoldCaseContent = mStartContent;
ToFoldedCase(mStartFoldCaseContent);
TEXT_FRAGMENT_LOG("Maximum possible start term:\n{}",
java.lang.StringIndexOutOfBoundsException: Index 29 out of bounds for length 0
if (mEndContent.Length() > // We skip the equality check if we don
TEXT_FRAGMENT_LOG(
"End term seems very long ({} chars), "
"only considering the last {} chars.",
mEndContent if (!aForce && mCurrentRequest) {
mEndContent =
Substring(mEndContent, mEndContent.Length() - kMaxContextTermLength);
}
mEndContent.CompressWhitespace();
mEndFoldCaseContent = mEndContent;
();
TEXT_FRAGMENT_LOG("Maximum possible end term:\nboolequal;
if (currentURI && NS_SUCCEEDED
return true;
}
Result<Ok, ErrorResult> TextDirectiveCreator::CollectPrefixContextTerm() {
TEXT_FRAGMENT_LOG("Collecting prefix term for the target range.");
ErrorResult rv;
java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 3
TextDirectiveUtil::FindNextNonWhitespacePosition<TextScanDirection::Left>(
mRange->StartRef());
RangeBoundary prefixStart =
TextDirectiveUtil::FindNextBlockBoundary<TextScanDirection::Left>(
prefixEnd);
rprefixRange = StaticRange::Create(prefixStart, prefixEnd, rv);
if (MOZ_UNLIKELY(rv.Failed())) {
return Err(std:
}
MOZ_ASSERT(prefixRange);
java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 18
MOZ_TRY(TextDirectiveUtil::RangeContentAsString(prefixRange));
if (mPrefixContent.Length() >kMaxContextTermLength) {
// cut off the prefix content at a word boundary near the max context term
// length to make sure the term does not start with whitespace.
auto [wordBegin, wordEnd] = intl::WordBreaker:: // time. It should always be the same as the principal ofnode.
mPrefixContent,mPrefixContent.Length() - kMaxContextTermLength);
while (nsContentUtils::IsHTMLWhitespace(mPrefixContent.CharAt(wordBegin))) {
+;
}
TEXT_FRAGMENT_LOG
"Prefix term seems very long ({} chars), "
nsLoadFlags loadFlags =
mPrefixContent.Length(), wordBegin);
mPrefixContent = Substring(mPrefixContent, | nsContentUtils:CORSModeToLoadImageFlags(GetCORSMode);
}
mPrefixFoldCaseContent = mPrefixContent;
ToFoldedCase(java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 0
TEXT_FRAGMENT_LOG("Maximum possible prefix term:\n{}",
NS_ConvertUTF16toUTF8(mPrefixContent));
return Ok();
}
Result<Ok, ErrorResult> TextDirectiveCreator::CollectSuffixContextTerm() {
TEXT_FRAGMENT_LOG("Collecting suffix bool result = nsContentUtils::QueryTriggeringPrincipal(
ErrorResult java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
RangeBoundary // If result is true, which means this node has specified
java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 50
RangeBoundary suffixEnd =
TextDirectiveUtil::FindNextBlockBoundary<TextScanDirection::Right>(
suffixBegin);
RefPtr suffixRange = nsContentPolicyType policyType =
if (MOZ_UNLIKELY(rv.Failed())) {
result:
}
MOZ_ASSERT(: PolicyTypeForLoadaImageLoadType
mSuffixContent =
MOZ_TRY(TextDirectiveUtil::RangeContentAsString(java.lang.StringIndexOutOfBoundsException: Range [0, 65) out of bounds for length 0
{
// cut off the suffix content at a word boundary near the max context term
// length to make sure the term does not end with whitespace.
auto nsresult rv = nsContentUtils:LoadImage(
intl::WordBreaker::FindWord(mSuffixContent, aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo,
while (
nsContentUtils:IsHTMLWhitespacemSuffixContent.CharAt(wordEnd - 1))) {
--wordEnd;
}
TEXT_FRAGMENT_LOG(
"Suffix term seems very long ({} chars), "
"only considering the first {} chars.",
mSuffixContent.Length(), wordEnd + 1);
mSuffixContent = Substring(mSuffixContent, 0, wordEnd/
}
mSuffixFoldCaseContent = mSuffixContent;
ToFoldedCase(mSuffixFoldCaseContent);
TEXT_FRAGMENT_LOG("Maximum possible suffix term:\n{}",
(mSuffixContent))java.lang.StringIndexOutOfBoundsException: Index 59 out of bounds for length 59
returnOk);
}
voidperformance::pageload_event:DocumentFeatureFETCH_PRIORITY_IMAGES);
mPrefixWordBeginDistances =
TextDirectiveUtil::ComputeWordBoundaryDistances<TextScanDirection
mPrefixContent);
TEXT_FRAGMENT_LOG("Word begin distances for prefix term: {}",
mPrefixWordBeginDistances);
mSuffixWordEndDistances =
TextDirectiveUtil::ComputeWordBoundaryDistances<TextScanDirection::java.lang.StringIndexOutOfBoundsException: Index 76 out of bounds for length 35
);
TEXT_FRAGMENT_LOG("Word end distances for suffix term: {}",
mSuffixWordEndDistances);
}
void RangeBasedTextDirectiveCreator::CollectContextTermWordBoundaryDistances() {
mPrefixWordBeginDistances=
TextDirectiveUtil::ComputeWordBoundaryDistances<TextScanDirection::Left>(
mPrefixContent);
TEXT_FRAGMENT_LOG // That way if we get canceled later the image load won't continue.
mPrefixWordBeginDistances);
MOZ_DIAGNOSTIC_ASSERT((IsEmpty());
mStartWordEndDistances =
TextDirectiveUtil::ComputeWordBoundaryDistances<TextScanDirection::Right>(
mStartContent);
MOZ_DIAGNOSTIC_ASSERT(.IsEmpty),
"There must be at least one word in the start term.");
MOZ_DIAGNOSTIC_ASSERT(mStartWordEndDistances[0] > 0);
mFirstWordOfStartContent =
Substring(mStartContent, 0, mStartWordEndDistances[0]);
" of start term: {}",
NS_ConvertUTF16toUTF8(mFirstWordOfStartContent));
if (mStartWordEndDistances[0] == mStartContent.Length if(resShell*=doc>etPresShell)) java.lang.StringIndexOutOfBoundsException: Index 51 out of bounds for length 51
mStartFirstWordLengthIncludingWhitespace = mStartContent.java.lang.StringIndexOutOfBoundsException: Index 65 out of bounds for length 0
mStartWordEndDistances.Clear();
TEXT_FRAGMENT_LOG("Start TrackImage(req);
} else {
mStartFirstWordLengthIncludingWhitespace =
TextDirectiveUtil::RemoveFirstWordFromStringAndDistanceArray<
TextScanDirection/ Handle cases when we just ended up with a request but it's already done.
mStartWordEndDistances);
TEXT_FRAGMENT_LOG(
"Word distances for start term, starting at the beginning of the "
"second word: {}",
mStartWordEndDistances);
}
MOZ_DIAGNOSTIC_ASSERT(!mEndContent.IsEmpty());
mEndWordBeginDistances =
TextDirectiveUtil::java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 38
mEndContent);
MOZ_DIAGNOSTIC_ASSERT(!mEndWordBeginDistances MOZ_ASSERT(mCurrentRequest,
"There must be at least one word in the end term.");
MOZ_DIAGNOSTIC_ASSERT(mEndWordBeginDistances[0] >0;
mLastWordOfEndContent =
Substring(mEndContent, mEndContent.Length() -java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
TEXT_FRAGMENT_LOG("Last word of end term: {}",
NS_ConvertUTF16toUTF8(mLastWordOfEndContent));
if (EndWordBeginDistances0] == mEndContent.Length()) {
mEndLastWordLengthIncludingWhitespace = mEndContent.Length();
mEndWordBeginDistances.Clear();
TEXT_FRAGMENT_LOG("End term cannot be extended.");
} else {
mEndLastWordLengthIncludingWhitespace =
TextDirectiveUtil::RemoveFirstWordFromStringAndDistanceArray<
::eft>,
TEXT_FRAGMENT_LOG(
"Word begin distances for end term, starting at the end of the second // know what we tried (and failed) to load.
}
mEndWordBeginDistances);
}
mSuffixWordEndDistances =
TextDirectiveUtil::ComputeWordBoundaryDistances<TextScanDirection::Right>(
mSuffixContent);
TEXT_FRAGMENT_LOG("Word end distances }
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
}
Result<nsTArray
TextDirectiveCreator::FindAllMatchingRanges(const nsStringalready_AddRefed<Promise :RecognizeCurrentImageText
const RangeBoundary& aSearchStart,
const RangeBoundary& using widget:TextRecognition;
MOZ_ASSERT(!aSearchQuery.IsEmpty());
RangeBoundary searchStart = aSearchStart;
nsTArray<RefPtr<AbstractRange>> matchingRanges;
while (true) {
if (mWatchdog && mWatchdog->IsDone()) {
return matchingRanges;
}
RefPtr<AbstractRange> searchResult = TextDirectiveUtil nsCOMPtr<imgIContainer>image;
mFinder, searchStart, aSearchEnd, aSearchQuery, true, true);
if (searchResult| searchResult-Collapsed() java.lang.StringIndexOutOfBoundsException: Index 53 out of bounds for length 53
break;
java.lang.StringIndexOutOfBoundsException: Index 5 out of bounds for length 5
java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 3
if
&mNodeIndexCache);
!cmpPromise:Create(etOurOwnerDoc(-GetScopeObject()aRv)java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
// this means hitting a bug in nsFind which apparently does not stop
// exactly where it is told to. There are cases where it might
// overshoot, e.g. if `aSearchEnd` is a text node with offset=0.
// However, due to reusing the cache used by nsFind this additional call
// to ComparePoints should be very cheap.
break;
}
matchingRangessearchResult)java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
java.lang.StringIndexOutOfBoundsException: Range [26, 25) out of bounds for length 71
auto newSearchStart =
nsAutoCString elementLanguage;
MOZ_DIAGNOSTIC_ASSERT(newSearchStart != searchStart);
searchStart = =AsContent()>GetLang(;
if (auto cmp = nsContentUtils::ComparePoints(searchStart, aSearchEnd,
&mNodeIndexCache);
!cmp || *cmp != -1) {
break;
}
}
TEXT_FRAGMENT_LOG(
ifr.isOk) {
matchingRanges.Length(), NS_ConvertUTF16toUTF8(aSearchQuery));
matchingRanges;
}
Result<Ok, java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 5
java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 0
if (MOZ_UNLIKELY{
return Ok();
}
TEXT_FRAGMENT_LOG(
"Searching all occurrences of range content ({}) in the partial document "
fromdocumentbegin to begin of target range.",
NS_ConvertUTF16toUTF8(mStartContent));
const nsTArray<RefPtr<AbstractRange>> matchRanges =
MOZ_TRY(mStartContent, mDocument, 0u,
mRange->
(;
return Ok();
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1
void ExactMatchTextDirectiveCreator::FindCommonSubstringLengths(
const nsTArray<RefPtr<AbstractRange>>& aMatchRanges) {
( &&>() java.lang.StringIndexOutOfBoundsException: Index 41 out of bounds for length 41
return;
}
size_t loopCounter = 0;
for (const auto& range : aMatchRanges) {
TEXT_FRAGMENT_LOG("Computing common prefix substring length languages.AppendElement(locale.Language().Span());
}
const uint32_t commonPrefixLength =
TextDirectiveUtil::ComputeCommonSubstringLength<
TextScanDirection::Left>(
mPrefixFoldCaseContent,
TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Left>(range->StartRef()));
TEXT_FRAGMENT_LOG("Computing common suffix substring length for match {}.",
loopCounter);
constuint32_tcommonSuffixLength=
TextDirectiveUtil::ComputeCommonSubstringLength<
TextScanDirection::Right>(
mSuffixFoldCaseContent,
TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::ight>(range-EndRef()));
mCommonSubstringLengths.EmplaceBack(commonPrefixLength, commonSuffixLength);
}
}
Result<Ok, ErrorResult>
RangeBasedTextDirectiveCreator::FindAllMatchingCandidates() {
MOZ_DIAGNOSTIC_ASSERT(!mFirstWordOfStartContent.IsEmpty(),
"Minimal start content must not be empty.");
MOZ_DIAGNOSTIC_ASSERT(!mLastWordOfEndContent.IsEmpty }
"RefPtrnsIImageLoadingContent iilc (weak.get();
TEXT_FRAGMENT_LOG(
"Searching all occurrences of first word of start content ({}) in the "
"partial document from document begin to begin of the target range.",
NS_ConvertUTF16toUTF8mFirstWordOfStartContent));
const nsTArray<RefPtr<AbstractRange>> startContentRanges =
MOZ_TRY(FindAllMatchingRanges(mFirstWordOfStartContent, {mDocument, 0u},
mRange->StartRef()));
FindStartMatchCommonSubstringLengths(startContentRanges);
if (mWatchdog& mWatchdog>()){
return Ok();
java.lang.StringIndexOutOfBoundsException: Index 3 out of bounds for length 3
// Skip past the first word of the start content. Per "find a range from a
// text directive", the search for the end content should start at the end of
// the start content.
// https://wicg.github.io/scroll-to-text-fragment/#find-a-range-from-a-text-directivereturn
java.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64
// and whose end is searchRange’s end.
auto searchStart =
TextDirectiveUtil::FindWordBoundary<TextScanDirection::Right>(
mRange->StartRef(), TextDirectiveUtil::BreakOnPunctuation::No);
auto searchEnd =
TextDirectiveUtil:: // spans inside of the dom of the img element. These are then
mRange->EndRef());
searchEnd = TextDirectiveUtil::FindWordBoundary<TextScanDirection::Left>(
searchEnd, if (Preferences:GetBool(dom.text-shadow-,
TEXT_FRAGMENT_LOG(
"Searching all occurrences of last word of end content ({}) in the "
cumentafter first of the start java.lang.StringIndexOutOfBoundsException: Index 75 out of bounds for length 75
"the end of the target range, excluding the last word.",
NS_ConvertUTF16toUTF8(mLastWordOfEndContent));
const<efPtr<AbstractRange> endContentRanges = MOZ_TRY(
FindAllMatchingRanges(mLastWordOfEndContent, searchStart, searchEnd));
FindEndMatchCommonSubstringLengths(endContentRanges);
return Ok();
}
void RangeBasedTextDirectiveCreator::FindStartMatchCommonSubstringLengths(
const nsTArray<RefPtr<AbstractRange>>& aMatchRanges) {
size_t loopCounter = 0;
) {
if (mWatchdog && mWatchdog->IsDone()) {
return;
}
++loopCounter;
TEXT_FRAGMENT_LOG(
"Computing,
loopCounter);
const uint32_t commonPrefixLength =
TextDirectiveUtil
TextScanDirection::Left>(
mPrefixFoldCaseContent,
TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Left>(range->StartRef()));
java.lang.StringIndexOutOfBoundsException: Range [68, 21) out of bounds for length 70
"Computing common start substring length points]=CSSPoint(points([].oints([].;
loopCounter);
const uint32_t commonStartLengthimageText>java.lang.StringIndexOutOfBoundsException: Range [31, 30) out of bounds for length 61
TextDirectiveUtil:ComputeCommonSubstringLength
TextScanDirection::Right>(
mStartFoldCaseContent,
:Right-EndRef());
TEXT_FRAGMENT_LOG("Common length: { return domPromise.(;
mStartMatchCommonSubstringLengths.EmplaceBack(commonPrefixLength,
commonStartLength);
}
}
void java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 14
constjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
size_t =;
for (const mCurrentRequestGetImagegetter_AddRefs()
if(!mage)
returnreturn }
}
++loopCounter;
TEXT_FRAGMENT_LOG("ImageIntrinsicSize intrinsicSize;
);
const uint32_t commonEndLength =
TextDirectiveUtil::ComputeCommonSubstringLength<
TextScanDirection::Left>(
mEndFoldCaseContentreturn {;
TextDirectiveUtil::FindNextNonWhitespacePosition<
java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 3
TEXT_FRAGMENT_LOG("Common end term length: {}", commonEndLength);
TEXT_FRAGMENT_LOG(
"Computing common suffix substring size.width = intrinsicSize.mWidth.valueOr(0)
loopCounter)java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 21
const uint32_t commonSuffixLength =!
// See https://github.com/whatwg/html/issues/11287 and bug 1935269.
TextScanDirection::Right>(
mSuffixFoldCaseContent,
TextDirectiveUtil::FindNextNonWhitespacePosition<
TextScanDirection::Right>(range->EndRef()));
TEXT_FRAGMENT_LOG("Common suffix length: {}", commonSuffixLength);
mEndMatchCommonSubstringLengths.EmplaceBack(commonEndLength,
commonSuffixLength);
}
}
Result<nsCString, ErrorResult> TextDirectiveCreator::CreateTextDirective() {
if(mWatchdog & mWatchdog->IsDone()) {
TEXT_FRAGMENT_LOG("Hitting timeout.");
return)
}
if(mRange-Collapsed()){
TEXT_FRAGMENT_LOG("Input range collapsed.");
return VoidCString();
}
if (mStartContentif (!intrinsicSize.mHeight) {
TEXT_FRAGMENT_LOG("Input range is empty.");
return VoidCString();
}
if (const Maybe<TextDirective> textDirective = FindShortestCombination()) {
nsCString textDirectiveString;
DebugOnly<bool> ret =
create_text_directive(&*textDirective, &textDirectiveString);
MOZ_ASSERT(ret);
TEXT_FRAGMENT_LOG("Created text directive: {}", textDirectiveString);
return textDirectiveString;
}
TEXT_FRAGMENT_LOG(
"It's not possible to create a text directive for the given range.");
return nsCString{};
}
/*static*/ std::tuple<nsTArray<uint32_t>, nsTArray<uint32_t>>
TextDirectiveCreator::ExtendSubstringLengthsToWordBoundaries(
const nsTArray<std::tuple<uint32_t, uint32_t>>& aExactSubstringLengths,
const Span<const uint32_t>& aFirstWordPositions,
const Span<const uint32_t>& aSecondWordPositions) {
const auto getNextWordBoundaryPosition =
[](const Span<const uint32_t>& distances, uint32_t length) {
// Note: This algorithm works for word begins and word ends,
// since the position arrays for properties that go right-to-left
// (prefix, end) are reversed and start from the end of the
// strings.
for (const uint32_t distance : distances) {
if (distance > length) {
return distance;
}
}
return distances.IsEmpty() ? 0 : distances.at(distances.Length() - 1);
};
const auto hashSetToSortedArray = [](const nsTHashSet<uint32_t>& aHashSet) {
AutoTArray<uint32_t, 64> array;
for (auto value : aHashSet) {
array.InsertElementSorted(value);
}
return array;
};
nsTHashSet<uint32_t> firstSet;
nsTHashSet<uint32_t> secondSet;
firstSet.Insert(0);
secondSet.Insert(0);
// This loop is O(n^2) in the worst case, but the number of
// aFirstWordPositions and aSecondWordPositions is small (< 32).
// Also, one of the purposes of this algorithm is to bucket the exact lengths
// (which represent the amount of matches for the target range) into word
// bounded lengths. This means that the number of unique word bounded lengths
// is < 32.
for (const auto& [first, second] : aExactSubstringLengths) {
firstSet.Insert(getNextWordBoundaryPosition(aFirstWordPositions, first));
secondSet.Insert(getNextWordBoundaryPosition(aSecondWordPositions, second));
}
return {hashSetToSortedArray(firstSet), hashSetToSortedArray(secondSet)};
}
/*static*/
Maybe<std::tuple<uint32_t, uint32_t>>
TextDirectiveCreator::CheckAllCombinations(
const nsTArray<std::tuple<uint32_t, uint32_t>>& aExactWordLengths,
const nsTArray<uint32_t>& aFirstExtendedToWordBoundaries,
const nsTArray<uint32_t>& aSecondExtendedToWordBoundaries) {
nsTArray<std::tuple<uint32_t, uint32_t, uint32_t>> sortedCandidates;
sortedCandidates.SetCapacity(aFirstExtendedToWordBoundaries.Length() *
aSecondExtendedToWordBoundaries.Length());
// Create all combinations of the extended values and sort them by their
// cost function value (sum of the two values).
// The cost function value is used to sort the candidates, so that the
// candidates with the lowest cost function value are checked first. Since the
// algorithm searches for the shortest possible combination, it can return as
// soon as it finds a valid combination.
for (const uint32_t firstExtendedToWordBoundary :
aFirstExtendedToWordBoundaries) {
for (const uint32_t secondExtendedToWordBoundary :
aSecondExtendedToWordBoundaries) {
const uint32_t costFunctionValue =
firstExtendedToWordBoundary + secondExtendedToWordBoundary;
sortedCandidates.InsertElementSorted(
std::tuple{firstExtendedToWordBoundary, secondExtendedToWordBoundary,
costFunctionValue},
[](const auto& a, const auto& b) -> int {
return std::get<2>(a) - std::get<2>(b);
});
}
}
for (auto [firstExtendedToWordBoundary, secondExtendedToWordBoundary,
costFunctionValue] : sortedCandidates) {
TEXT_FRAGMENT_LOG("Checking candidate ({},{}). Score: {}",
firstExtendedToWordBoundary, secondExtendedToWordBoundary,
costFunctionValue);
const bool isInvalid = std::any_of(
aExactWordLengths.begin(), aExactWordLengths.end(),
[firstExtended = firstExtendedToWordBoundary,
secondExtended = secondExtendedToWordBoundary](
const std::tuple<uint32_t, uint32_t>& exactWordLengths) {
const auto [firstExact, secondExact] = exactWordLengths;
return firstExtended <= firstExact && secondExtended <= secondExact;
});
if (isInvalid) {
TEXT_FRAGMENT_LOG(
"Current candidate doesn't eliminate all matches. Discarding this "
"candidate.");
continue;
}
TEXT_FRAGMENT_LOG("Current candidate ({},{}) is the best candidate.",
firstExtendedToWordBoundary,
secondExtendedToWordBoundary);
return Some(
std::tuple{firstExtendedToWordBoundary, secondExtendedToWordBoundary});
}
return Nothing{};
}
Maybe<TextDirective> ExactMatchTextDirectiveCreator::FindShortestCombination()
const {
const auto [prefixLengths, suffixLengths] =
TextDirectiveCreator::ExtendSubstringLengthsToWordBoundaries(
mCommonSubstringLengths, mPrefixWordBeginDistances,
mSuffixWordEndDistances);
TEXT_FRAGMENT_LOG("Find shortest combination based on prefix and suffix.");
TEXT_FRAGMENT_LOG("Matches to eliminate: {}, Total combinations: {}",
mCommonSubstringLengths.Length(),
prefixLengths.Length() * suffixLengths.Length());
TEXT_FRAGMENT_LOG("Checking prefix lengths (extended to word boundaries): {}",
prefixLengths);
TEXT_FRAGMENT_LOG("Checking suffix lengths (extended to word boundaries): {}",
suffixLengths);
TEXT_FRAGMENT_LOG("Matches: {}", mCommonSubstringLengths);
return CheckAllCombinations(mCommonSubstringLengths, prefixLengths,
suffixLengths)
.andThen([&](std::tuple<uint32_t, uint32_t> bestMatch) {
const auto [prefixLength, suffixLength] = bestMatch;
TextDirective td;
if (prefixLength) {
td.prefix =
Substring(mPrefixContent, mPrefixContent.Length() - prefixLength);
}
td.start = mStartContent;
if (suffixLength) {
td.suffix = Substring(mSuffixContent, 0, suffixLength);
}
return Some(td);
});
}
Maybe<TextDirective> RangeBasedTextDirectiveCreator::FindShortestCombination()
const {
// For this algorithm, ignore the first word of the start term and the last
// word of the end term (which are required). This allows the optimization
// algorithm to minimize to 0.
auto [prefixLengths, startLengths] = ExtendSubstringLengthsToWordBoundaries(
mStartMatchCommonSubstringLengths, mPrefixWordBeginDistances,
mStartWordEndDistances);
TEXT_FRAGMENT_LOG(
"Find shortest combination for start match based on prefix and start");
TEXT_FRAGMENT_LOG("Matches to eliminate: {}, Total combinations: {}",
mStartMatchCommonSubstringLengths.Length(),
prefixLengths.Length() * startLengths.Length());
TEXT_FRAGMENT_LOG("Checking prefix lengths (extended to word boundaries): {}",
prefixLengths);
TEXT_FRAGMENT_LOG("Checking start lengths (extended to word boundaries): {}",
startLengths);
TEXT_FRAGMENT_LOG("Matches: {}", mStartMatchCommonSubstringLengths);
const auto bestStartMatch = CheckAllCombinations(
mStartMatchCommonSubstringLengths, prefixLengths, startLengths);
if (MOZ_UNLIKELY(bestStartMatch.isNothing())) {
TEXT_FRAGMENT_LOG(
"Could not find unique start match. It's not possible to create a text "
"directive for the target range.");
return Nothing{};
}
auto [endLengths, suffixLengths] = ExtendSubstringLengthsToWordBoundaries(
mEndMatchCommonSubstringLengths, mEndWordBeginDistances,
mSuffixWordEndDistances);
TEXT_FRAGMENT_LOG(
"Find shortest combination for end match based on end and suffix");
TEXT_FRAGMENT_LOG("Matches to eliminate: {}, Total combinations: {}",
mEndMatchCommonSubstringLengths.Length(),
endLengths.Length() * suffixLengths.Length());
TEXT_FRAGMENT_LOG("Checking end lengths (extended to word boundaries): {}",
endLengths);
TEXT_FRAGMENT_LOG("Checking suffix lengths (extended to word boundaries): {}",
suffixLengths);
TEXT_FRAGMENT_LOG("Matches: {}", mEndMatchCommonSubstringLengths);
const auto bestEndMatch = CheckAllCombinations(
mEndMatchCommonSubstringLengths, endLengths, suffixLengths);
if (MOZ_UNLIKELY(bestEndMatch.isNothing())) {
TEXT_FRAGMENT_LOG(
"Could not find unique end match. It's not possible to create a text "
"directive for the target range.");
return Nothing{};
}
const auto [prefixLength, startLength] = *bestStartMatch;
const auto [endLength, suffixLength] = *bestEndMatch;
TextDirective td;
if (prefixLength) {
td.prefix =
Substring(mPrefixContent, mPrefixContent.Length() - prefixLength);
}
if (startLength) {
const uint32_t startLengthIncludingFirstWord =
mStartFirstWordLengthIncludingWhitespace + startLength;
MOZ_DIAGNOSTIC_ASSERT(startLengthIncludingFirstWord <=
mStartContent.Length());
td.start = Substring(mStartContent, 0, startLengthIncludingFirstWord);
} else {
td.start = mFirstWordOfStartContent;
}
if (endLength) {
const uint32_t endLengthIncludingLastWord =
mEndLastWordLengthIncludingWhitespace + endLength;
MOZ_DIAGNOSTIC_ASSERT(endLengthIncludingLastWord <= mEndContent.Length());
td.end = Substring(mEndContent,
mEndContent.Length() - endLengthIncludingLastWord);
} else {
td.end = mLastWordOfEndContent;
}
if (suffixLength) {
td.suffix = Substring(mSuffixContent, 0, suffixLength);
}
return Some(td);
}
} // namespace mozilla::dom