diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc | 443 |
1 files changed, 219 insertions, 224 deletions
diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc index 3c38e5fbcc2..3aa90bfd6fa 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc @@ -4,10 +4,9 @@ #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" -#include "base/strings/strcat.h" #include "base/time/default_tick_clock.h" -#include "components/shared_highlighting/core/common/disabled_sites.h" #include "components/shared_highlighting/core/common/shared_highlighting_features.h" #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h" #include "third_party/blink/public/common/browser_interface_broker_proxy.h" @@ -15,9 +14,11 @@ #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/finder/find_buffer.h" #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h" +#include "third_party/blink/renderer/core/editing/range_in_flat_tree.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h" #include "third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h" +#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h" #include "third_party/blink/renderer/platform/text/text_boundaries.h" using LinkGenerationError = shared_highlighting::LinkGenerationError; @@ -83,6 +84,23 @@ Node* NextNonEmptyVisibleTextNode(Node* start_node) { if (!start_node) return nullptr; + // Filter out nodes without layout object. + if (base::FeatureList::IsEnabled( + shared_highlighting::kSharedHighlightingLayoutObjectFix)) { + for (Node* node = start_node; node; node = Direction::Next(*node)) { + Node* next_node = Direction::GetVisibleTextNode(*node); + if (!next_node) + return nullptr; + if (next_node->GetLayoutObject() && + !PlainText(EphemeralRange::RangeOfContents(*next_node)) + .StripWhiteSpace() + .IsEmpty()) + return next_node; + node = next_node; + } + return nullptr; + } + // Move forward/backward until non empty visible text node is found. for (Node* node = start_node; node; node = Direction::Next(*node)) { Node* next_node = Direction::GetVisibleTextNode(*node); @@ -185,31 +203,87 @@ constexpr int kMinWordCount_ = 3; TextFragmentSelectorGenerator::TextFragmentSelectorGenerator( LocalFrame* main_frame) - : selection_frame_(main_frame) { - // Scroll-to-text doesn't support iframes. - DCHECK(main_frame->IsMainFrame()); + : frame_(main_frame) { + // Links are generally generated in the main frame except when the main frame + // delegates the text fragment to an iframe (e.g AMP Viewer pages). + if (!base::FeatureList::IsEnabled( + shared_highlighting::kSharedHighlightingAmp)) { + DCHECK(main_frame->IsMainFrame()); + } } -void TextFragmentSelectorGenerator::UpdateSelection( - const EphemeralRangeInFlatTree& selection_range) { - selection_range_ = MakeGarbageCollected<Range>( - selection_range.GetDocument(), - ToPositionInDOMTree(selection_range.StartPosition()), - ToPositionInDOMTree(selection_range.EndPosition())); - if (base::FeatureList::IsEnabled( - shared_highlighting::kPreemptiveLinkToTextGeneration) && - shared_highlighting::ShouldOfferLinkToText( - selection_frame_->GetDocument()->Url())) { - Reset(); - GenerateSelector(); +void TextFragmentSelectorGenerator::Generate(const RangeInFlatTree& range, + GenerateCallback callback) { + DCHECK(callback); + Reset(); + range_ = MakeGarbageCollected<RangeInFlatTree>(range.StartPosition(), + range.EndPosition()); + pending_generate_selector_callback_ = std::move(callback); + + StartGeneration(); +} + +void TextFragmentSelectorGenerator::Reset() { + if (finder_) + finder_->Cancel(); + + generation_start_time_ = base::DefaultTickClock::GetInstance()->NowTicks(); + state_ = kNotStarted; + error_.reset(); + step_ = kExact; + max_available_prefix_ = ""; + max_available_suffix_ = ""; + max_available_range_start_ = ""; + max_available_range_end_ = ""; + num_context_words_ = 0; + num_range_words_ = 0; + iteration_ = 0; + selector_ = nullptr; + range_ = nullptr; + pending_generate_selector_callback_.Reset(); +} + +void TextFragmentSelectorGenerator::Trace(Visitor* visitor) const { + visitor->Trace(frame_); + visitor->Trace(range_); + visitor->Trace(finder_); +} + +void TextFragmentSelectorGenerator::RecordSelectorStateUma() const { + base::UmaHistogramEnumeration("SharedHighlights.LinkGenerated.StateAtRequest", + state_); +} + +void TextFragmentSelectorGenerator::DidFindMatch( + const EphemeralRangeInFlatTree& match, + const TextFragmentAnchorMetrics::Match match_metrics, + bool is_unique) { + if (is_unique && + PlainText(match).StripWhiteSpace().length() == + PlainText(range_->ToEphemeralRange()).StripWhiteSpace().length()) { + state_ = kSuccess; + ResolveSelectorState(); + } else { + state_ = kNeedsNewCandidate; + + // If already tried exact selector then should continue by adding context. + if (step_ == kExact) + step_ = kContext; + GenerateSelectorCandidate(); } } +void TextFragmentSelectorGenerator::NoMatchFound() { + state_ = kFailure; + error_ = LinkGenerationError::kIncorrectSelector; + ResolveSelectorState(); +} + void TextFragmentSelectorGenerator::AdjustSelection() { - if (!selection_range_) + if (!range_) return; - EphemeralRangeInFlatTree ephemeral_range(selection_range_); + EphemeralRangeInFlatTree ephemeral_range = range_->ToEphemeralRange(); Node* start_container = ephemeral_range.StartPosition().ComputeContainerNode(); Node* end_container = ephemeral_range.EndPosition().ComputeContainerNode(); @@ -274,55 +348,45 @@ void TextFragmentSelectorGenerator::AdjustSelection() { } if (corrected_start != start_container || - corrected_start_offset != + static_cast<int>(corrected_start_offset) != ephemeral_range.StartPosition().ComputeOffsetInContainerNode() || corrected_end != end_container || - corrected_end_offset != + static_cast<int>(corrected_end_offset) != ephemeral_range.EndPosition().ComputeOffsetInContainerNode()) { - selection_range_ = MakeGarbageCollected<Range>( - selection_range_->OwnerDocument(), - Position(corrected_start, corrected_start_offset), - Position(corrected_end, corrected_end_offset)); - } -} - -void TextFragmentSelectorGenerator::Cancel() { - Reset(); -} - -void TextFragmentSelectorGenerator::RequestSelector( - RequestSelectorCallback callback) { - DCHECK(callback); - if (!base::FeatureList::IsEnabled( - shared_highlighting::kPreemptiveLinkToTextGeneration)) { - Reset(); - pending_generate_selector_callback_ = std::move(callback); - GenerateSelector(); - } else { - pending_generate_selector_callback_ = std::move(callback); - DCHECK_NE(state_, kNotStarted); - if (state_ == kFailure || state_ == kSuccess) { - selector_requested_before_ready_ = false; - if (state_ == kFailure) { - NotifyClientSelectorReady( - TextFragmentSelector(TextFragmentSelector::SelectorType::kInvalid)); - } else { - NotifyClientSelectorReady(*selector_); - } + PositionInFlatTree start(corrected_start, corrected_start_offset); + PositionInFlatTree end(corrected_end, corrected_end_offset); + + // TODO(bokan): This can sometimes occur from a selection. Avoid crashing + // from this case but this can come from a seemingly correct range so we + // should investigate the source of the bug. https://crbug.com/1216357 + if (start >= end) { + range_ = nullptr; return; } - selector_requested_before_ready_ = true; + + range_ = MakeGarbageCollected<RangeInFlatTree>(start, end); } } -void TextFragmentSelectorGenerator::GenerateSelector() { - DCHECK(selection_range_); +void TextFragmentSelectorGenerator::StartGeneration() { + DCHECK(range_); - selection_range_->OwnerDocument().UpdateStyleAndLayout( + range_->StartPosition().GetDocument()->UpdateStyleAndLayout( DocumentUpdateReason::kFindInPage); - // Shouldn't continue is selection is empty. - EphemeralRangeInFlatTree ephemeral_range(selection_range_); + // TODO(bokan): This can sometimes occur from a selection. Avoid crashing from + // this case but this can come from a seemingly correct range so we should + // investigate the source of the bug. + // https://crbug.com/1216357 + EphemeralRangeInFlatTree ephemeral_range = range_->ToEphemeralRange(); + if (ephemeral_range.StartPosition() >= ephemeral_range.EndPosition()) { + state_ = kFailure; + error_ = LinkGenerationError::kEmptySelection; + ResolveSelectorState(); + return; + } + + // Shouldn't continue if selection is empty. String selected_text = PlainText(ephemeral_range).StripWhiteSpace(); if (selected_text.IsEmpty()) { state_ = kFailure; @@ -332,9 +396,20 @@ void TextFragmentSelectorGenerator::GenerateSelector() { } AdjustSelection(); - UMA_HISTOGRAM_COUNTS_1000( - "SharedHighlights.LinkGenerated.SelectionLength", - PlainText(EphemeralRange(selection_range_)).length()); + + // TODO(bokan): This can sometimes occur from a selection. Avoid crashing from + // this case but this can come from a seemingly correct range so we should + // investigate the source of the bug. + // https://crbug.com/1216357 + if (!range_) { + state_ = kFailure; + error_ = LinkGenerationError::kEmptySelection; + ResolveSelectorState(); + return; + } + + UMA_HISTOGRAM_COUNTS_1000("SharedHighlights.LinkGenerated.SelectionLength", + PlainText(range_->ToEphemeralRange()).length()); state_ = kNeedsNewCandidate; GenerateSelectorCandidate(); } @@ -377,79 +452,87 @@ void TextFragmentSelectorGenerator::RunTextFinder() { iteration_++; // |FindMatch| will call |DidFindMatch| indicating if the match was unique. finder_ = MakeGarbageCollected<TextFragmentFinder>( - *this, *selector_, selection_frame_->GetDocument(), + *this, *selector_, frame_->GetDocument(), TextFragmentFinder::FindBufferRunnerType::kAsynchronous); finder_->FindMatch(); } -void TextFragmentSelectorGenerator::DidFindMatch( - const EphemeralRangeInFlatTree& match, - const TextFragmentAnchorMetrics::Match match_metrics, - bool is_unique) { - if (is_unique && PlainText(match).StripWhiteSpace().length() == - PlainText(EphemeralRangeInFlatTree(selection_range_)) - .StripWhiteSpace() - .length()) { - state_ = kSuccess; - ResolveSelectorState(); - } else { - state_ = kNeedsNewCandidate; - - // If already tried exact selector then should continue by adding context. - if (step_ == kExact) - step_ = kContext; - GenerateSelectorCandidate(); - } -} - -void TextFragmentSelectorGenerator::NoMatchFound() { - state_ = kFailure; - error_ = LinkGenerationError::kIncorrectSelector; - ResolveSelectorState(); -} +String TextFragmentSelectorGenerator::GetPreviousTextBlock( + const Position& prefix_end_position) { + Node* prefix_end = prefix_end_position.ComputeContainerNode(); + unsigned prefix_end_offset = + prefix_end_position.ComputeOffsetInContainerNode(); -void TextFragmentSelectorGenerator::OnSelectorReady( - const TextFragmentSelector& selector) { - // Check that frame is not deattched and generator is still valid. - DCHECK(selection_frame_); + // If given position point to the first visible text in its containiner node, + // use the preceding visible node for the suffix. + if (IsFirstVisiblePosition(prefix_end, prefix_end_offset)) { + prefix_end = BackwardNonEmptyVisibleTextNode( + FlatTreeTraversal::Previous(*prefix_end)); - RecordAllMetrics(selector); - if (pending_generate_selector_callback_) { - NotifyClientSelectorReady(selector); + if (!prefix_end) + return ""; + prefix_end_offset = prefix_end->textContent().length(); } -} -void TextFragmentSelectorGenerator::NotifyClientSelectorReady( - const TextFragmentSelector& selector) { - DCHECK(pending_generate_selector_callback_); - if (base::FeatureList::IsEnabled( - shared_highlighting::kPreemptiveLinkToTextGeneration)) - RecordPreemptiveGenerationMetrics(selector); - std::move(pending_generate_selector_callback_).Run(selector.ToString()); + // The furthest node within same block without crossing block boundaries would + // be the prefix start. + Node* prefix_start = LastVisibleTextNodeWithinBlock(prefix_end); + if (!prefix_start) + return ""; + + auto range_start = Position(prefix_start, 0); + auto range_end = Position(prefix_end, prefix_end_offset); + // TODO(gayane): Find test case when this happens, seems related to shadow + // root. See crbug.com/1220830 + if (range_start >= range_end) + return ""; + return PlainText(EphemeralRange(range_start, range_end)).StripWhiteSpace(); } -void TextFragmentSelectorGenerator::ClearSelection() { - if (selection_range_) { - selection_range_->Dispose(); - selection_range_ = nullptr; +String TextFragmentSelectorGenerator::GetNextTextBlock( + const Position& suffix_start_position) { + Node* suffix_start = suffix_start_position.ComputeContainerNode(); + unsigned suffix_start_offset = + suffix_start_position.ComputeOffsetInContainerNode(); + // If given position point to the last visible text in its containiner node, + // use the following visible node for the suffix. + if (IsLastVisiblePosition(suffix_start, suffix_start_offset)) { + suffix_start = FirstNonEmptyVisibleTextNode( + FlatTreeTraversal::NextSkippingChildren(*suffix_start)); + suffix_start_offset = 0; } -} + if (!suffix_start) + return ""; -void TextFragmentSelectorGenerator::Detach() { - Reset(); - selection_frame_ = nullptr; -} + // The furthest node within same block without crossing block boundaries would + // be the suffix end. + Node* suffix_end = FirstVisibleTextNodeWithinBlock(suffix_start); + if (!suffix_end) + return ""; -void TextFragmentSelectorGenerator::Trace(Visitor* visitor) const { - visitor->Trace(selection_frame_); - visitor->Trace(selection_range_); - visitor->Trace(finder_); + auto range_start = Position(suffix_start, suffix_start_offset); + auto range_end = Position(suffix_end, suffix_end->textContent().length()); + + // TODO(gayane): Find test case when this happens, seems related to shadow + // root. See crbug.com/1220830 + if (range_start >= range_end) + return ""; + return PlainText(EphemeralRange(range_start, range_end)).StripWhiteSpace(); } void TextFragmentSelectorGenerator::GenerateExactSelector() { DCHECK_EQ(kExact, step_); DCHECK_EQ(kNeedsNewCandidate, state_); - EphemeralRangeInFlatTree ephemeral_range(selection_range_); + EphemeralRangeInFlatTree ephemeral_range = range_->ToEphemeralRange(); + + // TODO(bokan): Another case where the range appears to not have valid nodes. + // Not sure how this happens. https://crbug.com/1216773. + if (!ephemeral_range.StartPosition().ComputeContainerNode() || + !ephemeral_range.EndPosition().ComputeContainerNode()) { + state_ = kFailure; + error_ = LinkGenerationError::kEmptySelection; + return; + } // If not in same block, should use ranges. if (!TextFragmentFinder::IsInSameUninterruptedBlock( @@ -488,7 +571,7 @@ void TextFragmentSelectorGenerator::ExtendRangeSelector() { // Initialize range start/end and word min count, if needed. if (max_available_range_start_.IsEmpty() && max_available_range_end_.IsEmpty()) { - EphemeralRangeInFlatTree ephemeral_range(selection_range_); + EphemeralRangeInFlatTree ephemeral_range = range_->ToEphemeralRange(); // If selection starts and ends in the same block, then split selected text // roughly in the middle. @@ -498,8 +581,8 @@ void TextFragmentSelectorGenerator::ExtendRangeSelector() { selection_text.Ensure16Bit(); int selection_length = selection_text.length(); int mid_point = - FindNextWordForward(selection_text.Characters16(), selection_length, - selection_length / 2); + FindNextWordForward(selection_text.Characters16(), + selection_text.length(), selection_length / 2); max_available_range_start_ = selection_text.Left(mid_point); // If from middle till end of selection there is no word break, then we @@ -516,9 +599,9 @@ void TextFragmentSelectorGenerator::ExtendRangeSelector() { // If not the same node, then we use first and last block of the selection // range. max_available_range_start_ = - GetNextTextBlock(selection_range_->StartPosition()); + GetNextTextBlock(ToPositionInDOMTree(range_->StartPosition())); max_available_range_end_ = - GetPreviousTextBlock(selection_range_->EndPosition()); + GetPreviousTextBlock(ToPositionInDOMTree(range_->EndPosition())); } // Use at least 3 words from both sides for more robust link to text. @@ -556,8 +639,9 @@ void TextFragmentSelectorGenerator::ExtendContext() { // Try initiating properties necessary for calculating prefix and suffix. if (max_available_prefix_.IsEmpty() && max_available_suffix_.IsEmpty()) { max_available_prefix_ = - GetPreviousTextBlock(selection_range_->StartPosition()); - max_available_suffix_ = GetNextTextBlock(selection_range_->EndPosition()); + GetPreviousTextBlock(ToPositionInDOMTree(range_->StartPosition())); + max_available_suffix_ = + GetNextTextBlock(ToPositionInDOMTree(range_->EndPosition())); // Use at least 3 words from both sides for more robust link to text. num_context_words_ = kMinWordCount_; @@ -585,97 +669,14 @@ void TextFragmentSelectorGenerator::ExtendContext() { state_ = kTestCandidate; } -String TextFragmentSelectorGenerator::GetPreviousTextBlock( - const Position& prefix_end_position) { - Node* prefix_end = prefix_end_position.ComputeContainerNode(); - unsigned prefix_end_offset = - prefix_end_position.ComputeOffsetInContainerNode(); - - // If given position point to the first visible text in its containiner node, - // use the preceding visible node for the suffix. - if (IsFirstVisiblePosition(prefix_end, prefix_end_offset)) { - prefix_end = BackwardNonEmptyVisibleTextNode( - FlatTreeTraversal::Previous(*prefix_end)); - - if (!prefix_end) - return ""; - prefix_end_offset = prefix_end->textContent().length(); - } - - // The furthest node within same block without crossing block boundaries would - // be the prefix start. - Node* prefix_start = LastVisibleTextNodeWithinBlock(prefix_end); - if (!prefix_start) - return ""; - - auto range_start = Position(prefix_start, 0); - auto range_end = Position(prefix_end, prefix_end_offset); - // TODO(gayane): Find test case when this happens, seems related to shadow - // root. See crbug.com/1220830 - if (range_start >= range_end) - return ""; - return PlainText(EphemeralRange(range_start, range_end)).StripWhiteSpace(); -} - -String TextFragmentSelectorGenerator::GetNextTextBlock( - const Position& suffix_start_position) { - Node* suffix_start = suffix_start_position.ComputeContainerNode(); - unsigned suffix_start_offset = - suffix_start_position.ComputeOffsetInContainerNode(); - // If given position point to the last visible text in its containiner node, - // use the following visible node for the suffix. - if (IsLastVisiblePosition(suffix_start, suffix_start_offset)) { - suffix_start = FirstNonEmptyVisibleTextNode( - FlatTreeTraversal::NextSkippingChildren(*suffix_start)); - suffix_start_offset = 0; - } - if (!suffix_start) - return ""; - - // The furthest node within same block without crossing block boundaries would - // be the suffix end. - Node* suffix_end = FirstVisibleTextNodeWithinBlock(suffix_start); - if (!suffix_end) - return ""; - - auto range_start = Position(suffix_start, suffix_start_offset); - auto range_end = Position(suffix_end, suffix_end->textContent().length()); - - // TODO(gayane): Find test case when this happens, seems related to shadow - // root. See crbug.com/1220830 - if (range_start >= range_end) - return ""; - return PlainText(EphemeralRange(range_start, range_end)).StripWhiteSpace(); -} - -void TextFragmentSelectorGenerator::Reset() { - if (finder_) - finder_->Cancel(); - - generation_start_time_ = base::DefaultTickClock::GetInstance()->NowTicks(); - state_ = kNotStarted; - error_.reset(); - step_ = kExact; - max_available_prefix_ = ""; - max_available_suffix_ = ""; - max_available_range_start_ = ""; - max_available_range_end_ = ""; - num_context_words_ = 0; - num_range_words_ = 0; - iteration_ = 0; - selector_ = nullptr; - selector_requested_before_ready_.reset(); - pending_generate_selector_callback_.Reset(); -} - void TextFragmentSelectorGenerator::RecordAllMetrics( const TextFragmentSelector& selector) { UMA_HISTOGRAM_BOOLEAN( "SharedHighlights.LinkGenerated", selector.Type() != TextFragmentSelector::SelectorType::kInvalid); - ukm::UkmRecorder* recorder = selection_frame_->GetDocument()->UkmRecorder(); - ukm::SourceId source_id = selection_frame_->GetDocument()->UkmSourceID(); + ukm::UkmRecorder* recorder = frame_->GetDocument()->UkmRecorder(); + ukm::SourceId source_id = frame_->GetDocument()->UkmSourceID(); if (selector.Type() != TextFragmentSelector::SelectorType::kInvalid) { UMA_HISTOGRAM_COUNTS_1000("SharedHighlights.LinkGenerated.ParamLength", @@ -707,27 +708,21 @@ void TextFragmentSelectorGenerator::RecordAllMetrics( } } -void TextFragmentSelectorGenerator::RecordPreemptiveGenerationMetrics( +void TextFragmentSelectorGenerator::OnSelectorReady( const TextFragmentSelector& selector) { - DCHECK(selector_requested_before_ready_.has_value()); - - bool success = - selector.Type() != TextFragmentSelector::SelectorType::kInvalid; + // Check that frame is not deattched and generator is still valid. + DCHECK(frame_); - std::string uma_prefix = "SharedHighlights.LinkGenerated"; - if (selector_requested_before_ready_.value()) { - uma_prefix = base::StrCat({uma_prefix, ".RequestedBeforeReady"}); - } else { - uma_prefix = base::StrCat({uma_prefix, ".RequestedAfterReady"}); + RecordAllMetrics(selector); + if (pending_generate_selector_callback_) { + NotifyClientSelectorReady(selector); } - base::UmaHistogramBoolean(uma_prefix, success); +} - if (!success) { - LinkGenerationError error = - error_.has_value() ? error_.value() : LinkGenerationError::kUnknown; - base::UmaHistogramEnumeration( - "SharedHighlights.LinkGenerated.Error.Requested", error); - } +void TextFragmentSelectorGenerator::NotifyClientSelectorReady( + const TextFragmentSelector& selector) { + DCHECK(pending_generate_selector_callback_); + std::move(pending_generate_selector_callback_).Run(selector); } } // namespace blink |