diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-16 09:59:13 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-05-20 10:28:53 +0000 |
commit | 6c11fb357ec39bf087b8b632e2b1e375aef1b38b (patch) | |
tree | c8315530db18a8ee566521c39ab8a6af4f72bc03 /chromium/third_party/blink/renderer/core/editing/finder | |
parent | 3ffaed019d0772e59d6cdb2d0d32fe4834c31f72 (diff) | |
download | qtwebengine-chromium-6c11fb357ec39bf087b8b632e2b1e375aef1b38b.tar.gz |
BASELINE: Update Chromium to 74.0.3729.159
Change-Id: I8d2497da544c275415aedd94dd25328d555de811
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/core/editing/finder')
8 files changed, 765 insertions, 187 deletions
diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.cc b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.cc index b3c08463402..a4f828bbf6c 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.cc +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.cc @@ -12,6 +12,7 @@ #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" +#include "third_party/blink/renderer/core/html/forms/html_select_element.h" #include "third_party/blink/renderer/core/invisible_dom/invisible_dom.h" #include "third_party/blink/renderer/core/layout/layout_block_flow.h" #include "third_party/blink/renderer/core/layout/layout_object.h" @@ -23,9 +24,9 @@ namespace blink { -FindBuffer::FindBuffer(const PositionInFlatTree& start_position) { - DCHECK(start_position.ComputeContainerNode()); - CollectTextUntilBlockBoundary(*start_position.ComputeContainerNode()); +FindBuffer::FindBuffer(const EphemeralRangeInFlatTree& range) { + DCHECK(range.IsNotNull() && !range.IsCollapsed()) << range; + CollectTextUntilBlockBoundary(range); } FindBuffer::Results::Results() { @@ -34,16 +35,17 @@ FindBuffer::Results::Results() { FindBuffer::Results::Results(const Vector<UChar>& buffer, String search_text, - const mojom::blink::FindOptions& options) { + const blink::FindOptions options) { // We need to own the |search_text| because |text_searcher_| only has a // StringView (doesn't own the search text). search_text_ = search_text; - text_searcher_.SetPattern(search_text_, options.match_case); + text_searcher_.SetPattern(search_text_, options); text_searcher_.SetText(buffer.data(), buffer.size()); text_searcher_.SetOffset(0); } -FindBuffer::Results::Iterator::Iterator(TextSearcherICU* text_searcher) +FindBuffer::Results::Iterator::Iterator(TextSearcherICU* text_searcher, + String search_text) : text_searcher_(text_searcher), has_match_(true) { operator++(); } @@ -63,13 +65,29 @@ FindBuffer::Results::Iterator FindBuffer::Results::begin() { if (empty_result_) return end(); text_searcher_.SetOffset(0); - return Iterator(&text_searcher_); + return Iterator(&text_searcher_, search_text_); } FindBuffer::Results::Iterator FindBuffer::Results::end() const { return Iterator(); } +bool FindBuffer::Results::IsEmpty() { + return begin() == end(); +} + +FindBuffer::BufferMatchResult FindBuffer::Results::front() { + return *begin(); +} + +FindBuffer::BufferMatchResult FindBuffer::Results::back() { + Iterator last_result; + for (Iterator it = begin(); it != end(); ++it) { + last_result = it; + } + return *last_result; +} + unsigned FindBuffer::Results::CountForTesting() { unsigned result = 0; for (Iterator it = begin(); it != end(); ++it) { @@ -79,15 +97,23 @@ unsigned FindBuffer::Results::CountForTesting() { } void FindBuffer::InvisibleLayoutScope::EnsureRecalc(Node& block_root) { + if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) + return; if (did_recalc_) return; did_recalc_ = true; DCHECK(block_root.GetDocument().Lifecycle().GetState() >= DocumentLifecycle::kStyleClean); + // If we're in an invisible subtree, we should recalc style from the invisible + // root/the highest ancestor of |block_root| with the invisible attribute, + // otherwise we should recalc from |block_root|. + // InvisibleRoot is always non-null when IsInsideInvisibleSubtree is true. if (InvisibleDOM::IsInsideInvisibleSubtree(block_root)) invisible_root_ = InvisibleDOM::InvisibleRoot(block_root); else invisible_root_ = &ToElement(block_root); + + DCHECK(invisible_root_); invisible_root_->GetDocument().SetFindInPageRoot(invisible_root_); invisible_root_->SetNeedsStyleRecalc( kSubtreeStyleChange, @@ -100,6 +126,8 @@ void FindBuffer::InvisibleLayoutScope::EnsureRecalc(Node& block_root) { } FindBuffer::InvisibleLayoutScope::~InvisibleLayoutScope() { + if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) + return; if (!did_recalc_) return; invisible_root_->GetDocument().SetFindInPageRoot(nullptr); @@ -117,17 +145,19 @@ bool ShouldIgnoreContents(const Node& node) { IsHTMLIFrameElement(element) || IsHTMLImageElement(element) || IsHTMLLegendElement(element) || IsHTMLMeterElement(element) || IsHTMLObjectElement(element) || IsHTMLProgressElement(element) || - IsHTMLSelectElement(element) || IsHTMLStyleElement(element) || - IsHTMLScriptElement(element) || IsHTMLVideoElement(element) || - IsHTMLAudioElement(element) || + (IsHTMLSelectElement(element) && + ToHTMLSelectElement(element).UsesMenuList()) || + IsHTMLStyleElement(element) || IsHTMLScriptElement(element) || + IsHTMLVideoElement(element) || IsHTMLAudioElement(element) || (element.GetDisplayLockContext() && - !element.GetDisplayLockContext()->IsSearchable()); + !element.GetDisplayLockContext()->IsActivatable()); } -Node* GetDisplayNoneAncestor(const Node& node) { +Node* GetNonSearchableAncestor(const Node& node) { for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(node)) { const ComputedStyle* style = ancestor.EnsureComputedStyle(); - if (style && style->Display() == EDisplay::kNone) + if ((style && style->Display() == EDisplay::kNone) || + ShouldIgnoreContents(ancestor)) return &ancestor; if (ancestor.IsDocumentNode()) return nullptr; @@ -144,7 +174,7 @@ bool IsBlock(EDisplay display) { Node* GetVisibleTextNode(Node& start_node) { Node* node = &start_node; // Move to outside display none subtree if we're inside one. - while (Node* ancestor = GetDisplayNoneAncestor(*node)) { + while (Node* ancestor = GetNonSearchableAncestor(*node)) { if (ancestor->IsDocumentNode()) return nullptr; node = FlatTreeTraversal::NextSkippingChildren(*ancestor); @@ -184,9 +214,56 @@ Node& GetLowestDisplayBlockInclusiveAncestor(const Node& start_node) { return *start_node.GetDocument().documentElement(); } +EphemeralRangeInFlatTree FindBuffer::FindMatchInRange( + const EphemeralRangeInFlatTree& range, + String search_text, + FindOptions options) { + if (!range.StartPosition().IsConnected()) + return EphemeralRangeInFlatTree(); + + EphemeralRangeInFlatTree last_match_range; + Node* first_node = range.StartPosition().NodeAsRangeFirstNode(); + Node* past_last_node = range.EndPosition().NodeAsRangePastLastNode(); + Node* node = first_node; + while (node && node != past_last_node) { + if (GetNonSearchableAncestor(*node)) { + node = FlatTreeTraversal::NextSkippingChildren(*node); + continue; + } + if (!node->IsTextNode()) { + node = FlatTreeTraversal::Next(*node); + continue; + } + // If we're in the same node as the start position, start from the start + // position instead of the start of this node. + PositionInFlatTree start_position = + node == first_node ? range.StartPosition() + : PositionInFlatTree::FirstPositionInNode(*node); + if (start_position >= range.EndPosition()) + break; + + FindBuffer buffer( + EphemeralRangeInFlatTree(start_position, range.EndPosition())); + std::unique_ptr<Results> match_results = + buffer.FindMatches(search_text, options); + if (!match_results->IsEmpty()) { + if (!(options & kBackwards)) { + BufferMatchResult match = match_results->front(); + return buffer.RangeFromBufferIndex(match.start, + match.start + match.length); + } + BufferMatchResult match = match_results->back(); + last_match_range = + buffer.RangeFromBufferIndex(match.start, match.start + match.length); + } + node = buffer.PositionAfterBlock().ComputeContainerNode(); + } + return last_match_range; +} + std::unique_ptr<FindBuffer::Results> FindBuffer::FindMatches( const WebString& search_text, - const mojom::blink::FindOptions& options) const { + const blink::FindOptions options) const { if (buffer_.IsEmpty() || search_text.length() > buffer_.size()) return std::make_unique<Results>(); String search_text_16_bit = search_text; @@ -195,12 +272,54 @@ std::unique_ptr<FindBuffer::Results> FindBuffer::FindMatches( return std::make_unique<Results>(buffer_, search_text_16_bit, options); } +bool FindBuffer::PushScopedForcedUpdateIfNeeded(const Element& element) { + if (auto* context = element.GetDisplayLockContext()) { + DCHECK(context->IsActivatable()); + scoped_forced_update_list_.push_back(context->GetScopedForcedUpdate()); + return true; + } + return false; +} + +void FindBuffer::CollectScopedForcedUpdates(Node& start_node, + const Node* search_range_end_node, + const Node* node_after_block) { + if (!RuntimeEnabledFeatures::DisplayLockingEnabled()) + return; + if (start_node.GetDocument().LockedDisplayLockCount() == + start_node.GetDocument().ActivationBlockingDisplayLockCount()) + return; + + Node* node = &start_node; + // We assume |start_node| is always visible/activatable if locked, so we don't + // need to check activatability of ancestors here. + for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*node)) { + if (!ancestor.IsElementNode()) + continue; + PushScopedForcedUpdateIfNeeded(ToElement(ancestor)); + } + + while (node && node != node_after_block && node != search_range_end_node) { + if (ShouldIgnoreContents(*node)) { + // Will skip display:none/non-activatable locked subtrees/etc. + node = FlatTreeTraversal::NextSkippingChildren(*node); + continue; + } + if (node->IsElementNode()) + PushScopedForcedUpdateIfNeeded(ToElement(*node)); + node = FlatTreeTraversal::Next(*node); + } +} + // Collects text until block boundary located at or after |start_node| // to |buffer_|. Saves the next starting node after the block to // |node_after_block_|. -void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { - // Get first visible text node from |start_node|. - Node* node = GetVisibleTextNode(start_node); +void FindBuffer::CollectTextUntilBlockBoundary( + const EphemeralRangeInFlatTree& range) { + DCHECK(range.IsNotNull() && !range.IsCollapsed()) << range; + // Get first visible text node from |start_position|. + Node* node = + GetVisibleTextNode(*range.StartPosition().NodeAsRangeFirstNode()); if (!node || !node->isConnected()) { node_after_block_ = nullptr; return; @@ -212,7 +331,8 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { // Calculate layout tree and style for invisible nodes inside the whole // subtree of |block_ancestor|. - if (node && InvisibleDOM::IsInsideInvisibleSubtree(*node)) + if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && node && + InvisibleDOM::IsInsideInvisibleSubtree(*node)) invisible_layout_scope_.EnsureRecalc(block_ancestor); // Collect all text under |block_ancestor| to |buffer_|, @@ -221,8 +341,23 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { // Will try to collect all text in outer div but will actually // stop when it encounters the inner div. So buffer will be "abc". Node* const first_traversed_node = node; + // We will also stop if we encountered/passed |end_node|. + Node* end_node = range.EndPosition().NodeAsRangeLastNode(); + + if (node) { + CollectScopedForcedUpdates(*node, end_node, just_after_block); + if (!scoped_forced_update_list_.IsEmpty()) + node->GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + } + while (node && node != just_after_block) { if (ShouldIgnoreContents(*node)) { + if (end_node && (end_node == node || + FlatTreeTraversal::IsDescendantOf(*end_node, *node))) { + // For setting |node_after_block| later. + node = FlatTreeTraversal::NextSkippingChildren(*node); + break; + } // Move the node so we wouldn't encounter this node or its descendants // later. if (!IsHTMLWBRElement(ToHTMLElement(*node))) @@ -230,7 +365,8 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { node = FlatTreeTraversal::NextSkippingChildren(*node); continue; } - if (node->IsElementNode() && ToElement(node)->HasInvisibleAttribute() && + if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && + node->IsElementNode() && ToElement(node)->HasInvisibleAttribute() && !invisible_layout_scope_.DidRecalc()) { // We found and invisible node. Calculate the layout & style for the whole // block at once, and we need to recalculate the NGOffsetMapping and start @@ -238,7 +374,6 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { mapping_needs_recalc_ = true; node = first_traversed_node; last_block_flow = nullptr; - offset_mapping_storage_ = nullptr; buffer_.clear(); invisible_layout_scope_.EnsureRecalc(block_ancestor); continue; @@ -248,6 +383,12 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { // This element and its descendants are not visible, skip it. // We can safely just check the computed style of this node since // we guarantee |block_ancestor| is visible. + if (end_node && (end_node == node || + FlatTreeTraversal::IsDescendantOf(*end_node, *node))) { + // For setting |node_after_block| later. + node = FlatTreeTraversal::NextSkippingChildren(*node); + break; + } node = FlatTreeTraversal::NextSkippingChildren(*node); if (node && !FlatTreeTraversal::IsDescendantOf(*node, block_ancestor)) break; @@ -270,10 +411,13 @@ void FindBuffer::CollectTextUntilBlockBoundary(Node& start_node) { break; } if (!last_block_flow) { - DCHECK(!offset_mapping_storage_); last_block_flow = &block_flow; } - AddTextToBuffer(text_node, block_flow); + AddTextToBuffer(text_node, block_flow, range); + } + if (node == end_node) { + node = FlatTreeTraversal::Next(*node); + break; } node = FlatTreeTraversal::Next(*node); } @@ -323,23 +467,27 @@ PositionInFlatTree FindBuffer::PositionAtEndOfCharacterAtIndex( } void FindBuffer::AddTextToBuffer(const Text& text_node, - LayoutBlockFlow& block_flow) { + LayoutBlockFlow& block_flow, + const EphemeralRangeInFlatTree& range) { if (!offset_mapping_ || mapping_needs_recalc_) { - offset_mapping_ = - NGInlineNode::GetOffsetMapping(&block_flow, &offset_mapping_storage_); + offset_mapping_ = NGInlineNode::GetOffsetMapping(&block_flow); mapping_needs_recalc_ = false; } - const String mapped_text = offset_mapping_->GetText(); - const NGMappingUnitRange range = - offset_mapping_->GetMappingUnitsForNode(text_node); + + Position node_start = + (&text_node == range.StartPosition().ComputeContainerNode()) + ? ToPositionInDOMTree(range.StartPosition().ToOffsetInAnchor()) + : Position::FirstPositionInNode(text_node); + Position node_end = + (&text_node == range.EndPosition().ComputeContainerNode()) + ? ToPositionInDOMTree(range.EndPosition().ToOffsetInAnchor()) + : Position::LastPositionInNode(text_node); unsigned last_unit_end = 0; bool first_unit = true; - for (const NGOffsetMappingUnit& unit : range) { - String text_for_unit = - mapped_text.Substring(unit.TextContentStart(), - unit.TextContentEnd() - unit.TextContentStart()); - text_for_unit.Ensure16Bit(); - text_for_unit.Replace('\n', kObjectReplacementCharacter); + const String mapped_text = offset_mapping_->GetText(); + for (const NGOffsetMappingUnit& unit : + offset_mapping_->GetMappingUnitsForDOMRange( + EphemeralRange(node_start, node_end))) { if (first_unit || last_unit_end != unit.TextContentStart()) { // This is the first unit, or the units are not consecutive, so we need to // insert a new BufferNodeMapping. @@ -347,6 +495,11 @@ void FindBuffer::AddTextToBuffer(const Text& text_node, BufferNodeMapping({buffer_.size(), unit.TextContentStart()})); first_unit = false; } + String text_for_unit = + mapped_text.Substring(unit.TextContentStart(), + unit.TextContentEnd() - unit.TextContentStart()); + text_for_unit.Ensure16Bit(); + text_for_unit.Replace('\n', kObjectReplacementCharacter); buffer_.Append(text_for_unit.Characters16(), text_for_unit.length()); last_unit_end = unit.TextContentEnd(); } diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.h b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.h index 80ea303f223..be236144f80 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.h +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer.h @@ -5,8 +5,8 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_FIND_BUFFER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_FIND_BUFFER_H_ -#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h" -#include "third_party/blink/renderer/core/editing/finder/find_buffer.h" +#include "third_party/blink/renderer/core/display_lock/display_lock_context.h" +#include "third_party/blink/renderer/core/editing/finder/find_options.h" #include "third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h" #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" @@ -23,13 +23,26 @@ class CORE_EXPORT FindBuffer { STACK_ALLOCATED(); public: - FindBuffer(const PositionInFlatTree& start_position); + explicit FindBuffer(const EphemeralRangeInFlatTree& range); + + static EphemeralRangeInFlatTree FindMatchInRange( + const EphemeralRangeInFlatTree& range, + String search_text, + const FindOptions); // A match result, containing the starting position of the match and // the length of the match. struct BufferMatchResult { const unsigned start; const unsigned length; + + bool operator==(const BufferMatchResult& other) const { + return start == other.start && length == other.length; + } + + bool operator!=(const BufferMatchResult& other) const { + return !operator==(other); + } }; // All match results for this buffer. We can iterate through the @@ -40,13 +53,13 @@ class CORE_EXPORT FindBuffer { Results(const Vector<UChar>& buffer, String search_text, - const mojom::blink::FindOptions& options); + const blink::FindOptions options); class CORE_EXPORT Iterator : public std::iterator<std::forward_iterator_tag, BufferMatchResult> { public: Iterator() = default; - Iterator(TextSearcherICU* text_searcher); + Iterator(TextSearcherICU* text_searcher, String search_text_); bool operator==(const Iterator& other) { return has_match_ == other.has_match_; @@ -70,18 +83,23 @@ class CORE_EXPORT FindBuffer { Iterator end() const; + bool IsEmpty(); + + BufferMatchResult front(); + + BufferMatchResult back(); + unsigned CountForTesting(); private: - bool empty_result_ = false; String search_text_; TextSearcherICU text_searcher_; + bool empty_result_ = false; }; // Finds all the match for |search_text| in |buffer_|. - std::unique_ptr<Results> FindMatches( - const WebString& search_text, - const mojom::blink::FindOptions& options) const; + std::unique_ptr<Results> FindMatches(const WebString& search_text, + const blink::FindOptions options) const; // Gets a flat tree range corresponding to text in the [start_index, // end_index) of |buffer|. @@ -95,12 +113,25 @@ class CORE_EXPORT FindBuffer { } private: - // Collects text for one LayoutBlockFlow located at or after |start_node| - // to |buffer_|, might be stopped without finishing one full LayoutBlockFlow - // if we encountered another LayoutBLockFlow. Saves the next starting node - // after the block (first node in another LayoutBlockFlow) to - // |node_after_block_|. - void CollectTextUntilBlockBoundary(Node& start_node); + // Collects text for one LayoutBlockFlow located within |range| to |buffer_|, + // might be stopped without finishing one full LayoutBlockFlow if we + // encountered another LayoutBLockFlow, or if the end of |range| is + // surpassed. Saves the next starting node after the block (first node in + // another LayoutBlockFlow or after |end_position|) to |node_after_block_|. + void CollectTextUntilBlockBoundary(const EphemeralRangeInFlatTree& range); + + // Adds the ScopedForcedUpdate of |element|'s DisplayLockContext (if it's + // there) to |scoped_forced_update_list_|. Returns true if we added a + // ScopedForceUpdate. + bool PushScopedForcedUpdateIfNeeded(const Element& element); + + // Collects all ScopedForceUpdates of any activatable-locked element + // within the range of [start_node, search_range_end_node] or + // [start_node, node_after_block) whichever is smaller, to + // |scoped_forced_update_list_|. + void CollectScopedForcedUpdates(Node& start_node, + const Node* search_range_end_node, + const Node* node_after_block); class CORE_EXPORT InvisibleLayoutScope { STACK_ALLOCATED(); @@ -153,12 +184,16 @@ class CORE_EXPORT FindBuffer { PositionInFlatTree PositionAtEndOfCharacterAtIndex(unsigned index) const; - void AddTextToBuffer(const Text& text_node, LayoutBlockFlow& block_flow); + // Adds text in |text_node| that are located within |range| to |buffer_|. + void AddTextToBuffer(const Text& text_node, + LayoutBlockFlow& block_flow, + const EphemeralRangeInFlatTree& range); InvisibleLayoutScope invisible_layout_scope_; Member<Node> node_after_block_; Vector<UChar> buffer_; Vector<BufferNodeMapping> buffer_node_mappings_; + Vector<DisplayLockContext::ScopedForcedUpdate> scoped_forced_update_list_; // For legacy layout, we need to save a unique_ptr of the NGOffsetMapping // because nobody owns it. In LayoutNG, the NGOffsetMapping is owned by diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc index 71cb1338c28..88638e533e5 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_buffer_test.cc @@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/editing/finder/find_buffer.h" +#include "build/build_config.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/selection_template.h" #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h" @@ -12,9 +13,18 @@ namespace blink { class FindBufferTest : public EditingTestBase { protected: - PositionInFlatTree FirstPosition() { - return PositionInFlatTree::FirstPositionInNode( - *GetDocument().documentElement()); + PositionInFlatTree LastPositionInDocument() { + return GetDocument().documentElement()->lastChild() + ? PositionInFlatTree::AfterNode( + *GetDocument().documentElement()->lastChild()) + : PositionInFlatTree::LastPositionInNode( + *GetDocument().documentElement()); + } + + EphemeralRangeInFlatTree WholeDocumentRange() { + return EphemeralRangeInFlatTree(PositionInFlatTree::FirstPositionInNode( + *GetDocument().documentElement()), + LastPositionInDocument()); } PositionInFlatTree PositionFromParentId(const char* id, unsigned offset) { @@ -32,10 +42,10 @@ TEST_F(FindBufferTest, FindInline) { "<div id='container'>a<span id='span'>b</span><b id='b'>c</b><div " "id='none' style='display:none'>d</div><div id='inline-div' " "style='display: inline;'>e</div></div>"); - FindBuffer buffer(FirstPosition()); + FindBuffer buffer(WholeDocumentRange()); EXPECT_TRUE(buffer.PositionAfterBlock().IsNull()); std::unique_ptr<FindBuffer::Results> results = - buffer.FindMatches("abce", *mojom::blink::FindOptions::New()); + buffer.FindMatches("abce", kCaseInsensitive); EXPECT_EQ(1u, results->CountForTesting()); FindBuffer::BufferMatchResult match = *results->begin(); EXPECT_EQ(0u, match.start); @@ -51,7 +61,7 @@ TEST_F(FindBufferTest, RangeFromBufferIndex) { "<div id='container'>a <span id='span'> b</span><b id='b'>cc</b><div " "id='none' style='display:none'>d</div><div id='inline-div' " "style='display: inline;'>e</div></div>"); - FindBuffer buffer(FirstPosition()); + FindBuffer buffer(WholeDocumentRange()); // Range for "a" EXPECT_EQ(EphemeralRangeInFlatTree(PositionFromParentId("container", 0), PositionFromParentId("container", 1)), @@ -126,155 +136,513 @@ TEST_F(FindBufferTest, RangeFromBufferIndex) { SerializeRange(buffer.RangeFromBufferIndex(1, 4))); } +TEST_F(FindBufferTest, FindBetweenPositionsSameNode) { + PositionInFlatTree start_position = + ToPositionInFlatTree(SetCaretTextToBody("f|oofoo")); + Node* node = start_position.ComputeContainerNode(); + // |end_position| = foofoo| (end of text). + PositionInFlatTree end_position = + PositionInFlatTree::LastPositionInNode(*node); + FindBuffer buffer(EphemeralRangeInFlatTree(start_position, end_position)); + EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(4u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // |start_position| = fo|ofoo + // |end_position| = foof|oo + start_position = PositionInFlatTree(*node, 2u); + end_position = PositionInFlatTree(*node, 4u); + buffer = FindBuffer(EphemeralRangeInFlatTree(start_position, end_position)); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, FindBetweenPositionsDifferentNodes) { + SetBodyContent( + "<div id='div'>foo<span id='span'>foof<b id='b'>oo</b></span></div>"); + Element* div = GetElementById("div"); + Element* span = GetElementById("span"); + Element* b = GetElementById("b"); + // <div>^foo<span>foof|<b>oo</b></span></div> + // So buffer = "foofoof" + FindBuffer buffer(EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*div->firstChild()), + PositionInFlatTree::LastPositionInNode(*span->firstChild()))); + EXPECT_EQ(2u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(4u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(3u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>f^oo<span>foof<b>o|o</b></span></div> + // So buffer = "oofoofo" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*div->firstChild(), 1), + PositionInFlatTree(*b->firstChild(), 1))); + EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(5u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>foo<span>f^oof|<b>oo</b></span></div> + // So buffer = "oof" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1), + PositionInFlatTree(*span->firstChild(), 4))); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>foo<span>foof^<b>oo|</b></span></div> + // So buffer = "oo" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 4), + PositionInFlatTree(*b->firstChild(), 2))); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, FindBetweenPositionsSkippedNodes) { + SetBodyContent( + "<div id='div'>foo<span id='span' style='display:none'>foof</span><b " + "id='b'>oo</b><script id='script'>fo</script><a id='a'>o</o></div>"); + Element* div = GetElementById("div"); + Element* span = GetElementById("span"); + Element* b = GetElementById("b"); + Element* script = GetElementById("script"); + Element* a = GetElementById("a"); + + // <div>^foo<span style='display:none;'>foo|f</span><b>oo</b> + // <script>fo</script><a>o</a></div> + // So buffer = "foo" + FindBuffer buffer(EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*div->firstChild()), + PositionInFlatTree(*span->firstChild(), 3))); + EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("oof", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("fo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>foo<span style='display:none;'>f^oof</span><b>oo|</b> + // <script>fo</script><a>o</a></div> + // So buffer = "oo" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1), + PositionInFlatTree(*b->firstChild(), 2))); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>foo<span style='display:none;'>f^oof</span><b>oo|</b> + // <script>f|o</script><a>o</a></div> + // So buffer = "oo" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*span->firstChild(), 1), + PositionInFlatTree(*script->firstChild(), 2))); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(2u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); + // <div>foo<span style='display:none;'>foof</span><b>oo|</b> + // <script>f^o</script><a>o|</a></div> + // So buffer = "o" + buffer = FindBuffer( + EphemeralRangeInFlatTree(PositionInFlatTree(*script->firstChild(), 1), + PositionInFlatTree(*a->firstChild(), 1))); + EXPECT_EQ(0u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("oo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("o", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("f", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, FindMatchInRange) { + SetBodyContent( + "<div id='div' invisible>foo<a id='a'>foof</a><b id='b'>oo</b></div>"); + Element* div = GetElementById("div"); + Element* a = GetElementById("a"); + Element* b = GetElementById("b"); + EphemeralRangeInFlatTree foo1 = EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*div->firstChild()), + PositionInFlatTree::LastPositionInNode(*div->firstChild())); + EphemeralRangeInFlatTree foo2 = EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*a->firstChild()), + PositionInFlatTree(*a->firstChild(), 3)); + EphemeralRangeInFlatTree foo3 = EphemeralRangeInFlatTree( + PositionInFlatTree(*a->firstChild(), 3), + PositionInFlatTree::LastPositionInNode(*b->firstChild())); + + // <div>^foo<a>foof</a><b>oo|</b></div>, forwards + EphemeralRangeInFlatTree match = FindBuffer::FindMatchInRange( + WholeDocumentRange(), "foo", kCaseInsensitive); + EXPECT_EQ(foo1, match); + // <div>f^oo<a>foof</a><b>oo|</b></div>, forwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree(PositionInFlatTree(*div->firstChild(), 1), + LastPositionInDocument()), + "foo", kCaseInsensitive); + EXPECT_EQ(foo2, match); + // <div>foo<a>^foo|f</a><b>oo</b></div>, forwards + match = FindBuffer::FindMatchInRange(foo2, "foo", kCaseInsensitive); + EXPECT_EQ(foo2, match); + // <div>foo<a>f^oof|</a><b>oo</b></div>, forwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree( + PositionInFlatTree(*a->firstChild(), 1), + PositionInFlatTree::LastPositionInNode(*a->firstChild())), + "foo", kCaseInsensitive); + EXPECT_TRUE(match.IsNull()); + // <div>foo<a>f^oof</a><b>oo|</b></div>, forwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree(PositionInFlatTree(*a->firstChild(), 1), + LastPositionInDocument()), + "foo", kCaseInsensitive); + EXPECT_EQ(foo3, match); + + // <div>^foo<a>foof</a><b>oo|</b></div>, backwards + match = FindBuffer::FindMatchInRange(WholeDocumentRange(), "foo", + kCaseInsensitive | kBackwards); + EXPECT_EQ(foo3, match); + // <div>^foo<a>foof</a><b>o|o</b></div>, backwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*div->firstChild()), + PositionInFlatTree(*b->firstChild(), 1)), + "foo", kCaseInsensitive | kBackwards); + EXPECT_EQ(foo2, match); + // <div>foo<a>^foof</a><b>o|o</b></div>, backwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*a->firstChild()), + PositionInFlatTree(*b->firstChild(), 1)), + "foo", kCaseInsensitive | kBackwards); + EXPECT_EQ(foo2, match); + // <div>foo<a>foo^f</a><b>o|o</b></div>, backwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree(PositionInFlatTree(*a->firstChild(), 3), + PositionInFlatTree(*b->firstChild(), 1)), + "foo", kCaseInsensitive | kBackwards); + EXPECT_TRUE(match.IsNull()); + // <div>^foo<a>fo|of</a><b>oo</b></div>, backwards + match = FindBuffer::FindMatchInRange( + EphemeralRangeInFlatTree( + PositionInFlatTree::FirstPositionInNode(*div->firstChild()), + PositionInFlatTree(*a->firstChild(), 2)), + "foo", kCaseInsensitive | kBackwards); + EXPECT_EQ(foo1, match); +} + class FindBufferBlockTest : public FindBufferTest, public testing::WithParamInterface<std::string> {}; -INSTANTIATE_TEST_CASE_P(Blocks, - FindBufferBlockTest, - testing::Values("block", - "table", - "flow-root", - "grid", - "flex", - "list-item")); +INSTANTIATE_TEST_SUITE_P(Blocks, + FindBufferBlockTest, + testing::Values("block", + "table", + "flow-root", + "grid", + "flex", + "list-item")); TEST_P(FindBufferBlockTest, FindBlock) { SetBodyContent("text<div id='block' style='display: " + GetParam() + ";'>block</div><span id='span'>span</span>"); - FindBuffer text_buffer(FirstPosition()); + FindBuffer text_buffer(WholeDocumentRange()); EXPECT_EQ(GetElementById("block"), *text_buffer.PositionAfterBlock().ComputeContainerNode()); - EXPECT_EQ(1u, - text_buffer.FindMatches("text", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, text_buffer - .FindMatches("textblock", *mojom::blink::FindOptions::New()) - ->CountForTesting()); EXPECT_EQ( - 0u, - text_buffer.FindMatches("text block", *mojom::blink::FindOptions::New()) - ->CountForTesting()); + 1u, text_buffer.FindMatches("text", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, text_buffer.FindMatches("textblock", kCaseInsensitive) + ->CountForTesting()); + EXPECT_EQ(0u, text_buffer.FindMatches("text block", kCaseInsensitive) + ->CountForTesting()); - FindBuffer block_buffer(text_buffer.PositionAfterBlock()); + FindBuffer block_buffer(EphemeralRangeInFlatTree( + text_buffer.PositionAfterBlock(), LastPositionInDocument())); EXPECT_EQ(GetElementById("span"), *block_buffer.PositionAfterBlock().ComputeContainerNode()); EXPECT_EQ( - 1u, block_buffer.FindMatches("block", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, block_buffer - .FindMatches("textblock", *mojom::blink::FindOptions::New()) + 1u, + block_buffer.FindMatches("block", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, block_buffer.FindMatches("textblock", kCaseInsensitive) ->CountForTesting()); - EXPECT_EQ( - 0u, - block_buffer.FindMatches("text block", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, block_buffer - .FindMatches("blockspan", *mojom::blink::FindOptions::New()) + EXPECT_EQ(0u, block_buffer.FindMatches("text block", kCaseInsensitive) + ->CountForTesting()); + EXPECT_EQ(0u, block_buffer.FindMatches("blockspan", kCaseInsensitive) + ->CountForTesting()); + EXPECT_EQ(0u, block_buffer.FindMatches("block span", kCaseInsensitive) ->CountForTesting()); - EXPECT_EQ( - 0u, - block_buffer.FindMatches("block span", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - FindBuffer span_buffer(block_buffer.PositionAfterBlock()); + FindBuffer span_buffer(EphemeralRangeInFlatTree( + block_buffer.PositionAfterBlock(), LastPositionInDocument())); EXPECT_TRUE(span_buffer.PositionAfterBlock().IsNull()); - EXPECT_EQ(1u, - span_buffer.FindMatches("span", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, span_buffer - .FindMatches("blockspan", *mojom::blink::FindOptions::New()) - ->CountForTesting()); EXPECT_EQ( - 0u, - span_buffer.FindMatches("block span", *mojom::blink::FindOptions::New()) - ->CountForTesting()); + 1u, span_buffer.FindMatches("span", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, span_buffer.FindMatches("blockspan", kCaseInsensitive) + ->CountForTesting()); + EXPECT_EQ(0u, span_buffer.FindMatches("block span", kCaseInsensitive) + ->CountForTesting()); } class FindBufferSeparatorTest : public FindBufferTest, public testing::WithParamInterface<std::string> {}; -INSTANTIATE_TEST_CASE_P(Separators, - FindBufferSeparatorTest, - testing::Values("br", - "hr", - "legend", - "meter", - "object", - "progress", - "select", - "video")); +INSTANTIATE_TEST_SUITE_P(Separators, + FindBufferSeparatorTest, + testing::Values("br", + "hr", + "legend", + "meter", + "object", + "progress", + "select", + "video")); TEST_P(FindBufferSeparatorTest, FindSeparatedElements) { SetBodyContent("a<" + GetParam() + ">a</" + GetParam() + ">a"); - FindBuffer buffer(FirstPosition()); - EXPECT_EQ(0u, buffer.FindMatches("aa", *mojom::blink::FindOptions::New()) - ->CountForTesting()); + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(0u, buffer.FindMatches("aa", kCaseInsensitive)->CountForTesting()); } TEST_F(FindBufferTest, WhiteSpaceCollapsingPreWrap) { SetBodyContent( " a \n b <b> c </b> d <span style='white-space: pre-wrap'> e " "</span>"); - FindBuffer buffer(FirstPosition()); + FindBuffer buffer(WholeDocumentRange()); EXPECT_EQ( - 1u, buffer.FindMatches("a b c d e ", *mojom::blink::FindOptions::New()) - ->CountForTesting()); -}; + 1u, + buffer.FindMatches("a b c d e ", kCaseInsensitive)->CountForTesting()); +} TEST_F(FindBufferTest, WhiteSpaceCollapsingPre) { SetBodyContent("<div style='white-space: pre;'>a \n b</div>"); - FindBuffer buffer(FirstPosition()); - EXPECT_EQ(1u, buffer.FindMatches("a", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(1u, buffer.FindMatches("b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("ab", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a\n b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a \nb", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a \n b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(1u, buffer.FindMatches("a", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("ab", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a\n b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a \nb", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a \n b", kCaseInsensitive)->CountForTesting()); } TEST_F(FindBufferTest, WhiteSpaceCollapsingPreLine) { SetBodyContent("<div style='white-space: pre-line;'>a \n b</div>"); - FindBuffer buffer(FirstPosition()); - EXPECT_EQ(1u, buffer.FindMatches("a", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(1u, buffer.FindMatches("b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("ab", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a b", *mojom::blink::FindOptions::New()) - ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a \n b", *mojom::blink::FindOptions::New()) + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(1u, buffer.FindMatches("a", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("ab", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a \n b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a\n b", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a \nb", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, + buffer.FindMatches("a\nb", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, BidiTest) { + SetBodyContent("<bdo dir=rtl id=bdo>foo<span>bar</span></bdo>"); + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(1u, + buffer.FindMatches("foobar", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, KanaSmallVsNormal) { + SetBodyContent("や"); // Normal-sized や + FindBuffer buffer(WholeDocumentRange()); + // Should find normal-sized や + EXPECT_EQ(1u, buffer.FindMatches("や", kCaseInsensitive)->CountForTesting()); + // Should not find smalll-sized ゃ + EXPECT_EQ(0u, buffer.FindMatches("ゃ", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, KanaDakuten) { + SetBodyContent("びゃ"); // Hiragana bya + FindBuffer buffer(WholeDocumentRange()); + // Should find bi + EXPECT_EQ(1u, buffer.FindMatches("び", kCaseInsensitive)->CountForTesting()); + // Should find smalll-sized ゃ + EXPECT_EQ(1u, buffer.FindMatches("ゃ", kCaseInsensitive)->CountForTesting()); + // Should find bya + EXPECT_EQ(1u, + buffer.FindMatches("びゃ", kCaseInsensitive)->CountForTesting()); + // Should not find hi + EXPECT_EQ(0u, buffer.FindMatches("ひ", kCaseInsensitive)->CountForTesting()); + // Should not find pi + EXPECT_EQ(0u, buffer.FindMatches("ぴ", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, KanaHalfFull) { + // Should treat hiragana, katakana, half width katakana as the same. + // hiragana ra, half width katakana ki, full width katakana na + SetBodyContent("らキナ"); + FindBuffer buffer(WholeDocumentRange()); + // Should find katakana ra + EXPECT_EQ(1u, buffer.FindMatches("ラ", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("ラ", kCaseInsensitive)->CountForTesting()); + // Should find hiragana & katakana ki + EXPECT_EQ(1u, buffer.FindMatches("き", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("キ", kCaseInsensitive)->CountForTesting()); + // Should find hiragana & katakana na + EXPECT_EQ(1u, buffer.FindMatches("な", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("ナ", kCaseInsensitive)->CountForTesting()); + // Should find whole word + EXPECT_EQ(1u, + buffer.FindMatches("らきな", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("ラキナ", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, + buffer.FindMatches("ラキナ", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, WholeWordTest) { + SetBodyContent("foo bar foobar 六本木"); + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(2u, buffer.FindMatches("foo", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("foo", kCaseInsensitive | kWholeWord) ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a\n b", *mojom::blink::FindOptions::New()) + EXPECT_EQ(2u, buffer.FindMatches("bar", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("bar", kCaseInsensitive | kWholeWord) ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a \nb", *mojom::blink::FindOptions::New()) + EXPECT_EQ(1u, buffer.FindMatches("六", kCaseInsensitive | kWholeWord) ->CountForTesting()); - EXPECT_EQ(0u, buffer.FindMatches("a\nb", *mojom::blink::FindOptions::New()) + EXPECT_EQ(1u, buffer.FindMatches("本木", kCaseInsensitive | kWholeWord) ->CountForTesting()); } -TEST_F(FindBufferTest, BidiTest) { - SetBodyContent("<bdo dir=rtl id=bdo>foo<span>bar</span></bdo>"); - FindBuffer buffer(FirstPosition()); - EXPECT_EQ(1u, buffer.FindMatches("foobar", *mojom::blink::FindOptions::New()) - ->CountForTesting()); +TEST_F(FindBufferTest, KanaDecomposed) { + SetBodyContent("は ゛"); + FindBuffer buffer(WholeDocumentRange()); + EXPECT_EQ(0u, buffer.FindMatches("ば", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, + buffer.FindMatches("は ゛", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(0u, buffer.FindMatches("バ ", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, + buffer.FindMatches("ハ ゛", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, buffer.FindMatches("ハ ゙", kCaseInsensitive)->CountForTesting()); + EXPECT_EQ(1u, + buffer.FindMatches("ハ ゛", kCaseInsensitive)->CountForTesting()); +} + +TEST_F(FindBufferTest, FindPlainTextInvalidTarget1) { + static const char* body_content = "<div>foo bar test</div>"; + SetBodyContent(body_content); + + // A lone lead surrogate (0xDA0A) example taken from fuzz-58. + static const UChar kInvalid1[] = {0x1461u, 0x2130u, 0x129bu, 0xd711u, 0xd6feu, + 0xccadu, 0x7064u, 0xd6a0u, 0x4e3bu, 0x03abu, + 0x17dcu, 0xb8b7u, 0xbf55u, 0xfca0u, 0x07fau, + 0x0427u, 0xda0au, 0}; + + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches(String(kInvalid1), 0); + EXPECT_TRUE(results->IsEmpty()); +} + +TEST_F(FindBufferTest, FindPlainTextInvalidTarget2) { + static const char* body_content = "<div>foo bar test</div>"; + SetBodyContent(body_content); + + // A lone trailing surrogate (U+DC01). + static const UChar kInvalid2[] = {0x1461u, 0x2130u, 0x129bu, 0xdc01u, + 0xd6feu, 0xccadu, 0}; + + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches(String(kInvalid2), 0); + EXPECT_TRUE(results->IsEmpty()); +} + +TEST_F(FindBufferTest, FindPlainTextInvalidTarget3) { + static const char* body_content = "<div>foo bar test</div>"; + SetBodyContent(body_content); + + // A trailing surrogate followed by a lead surrogate (U+DC03 U+D901). + static const UChar kInvalid3[] = {0xd800u, 0xdc00u, 0x0061u, 0xdc03u, + 0xd901u, 0xccadu, 0}; + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches(String(kInvalid3), 0); + EXPECT_TRUE(results->IsEmpty()); +} + +TEST_F(FindBufferTest, DisplayInline) { + SetBodyContent("<span>fi</span>nd"); + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches("find", 0); + ASSERT_EQ(1u, results->CountForTesting()); + EXPECT_EQ(FindBuffer::BufferMatchResult({0, 4}), results->front()); +} + +TEST_F(FindBufferTest, DisplayBlock) { + SetBodyContent("<div>fi</div>nd"); + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches("find", 0); + ASSERT_EQ(0u, results->CountForTesting()) + << "We should not match across block."; +} + +TEST_F(FindBufferTest, DisplayContents) { + SetBodyContent("<div style='display: contents'>fi</div>nd"); + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches("find", 0); + ASSERT_EQ(1u, results->CountForTesting()); + EXPECT_EQ(FindBuffer::BufferMatchResult({0, 4}), results->front()); +} + +TEST_F(FindBufferTest, WBRTest) { + SetBodyContent("fi<wbr>nd and fin<wbr>d"); + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches("find", 0); + ASSERT_EQ(2u, results->CountForTesting()); +} + +TEST_F(FindBufferTest, InputTest) { + SetBodyContent("fi<input type='text'>nd and fin<input type='text'>d"); + FindBuffer buffer(WholeDocumentRange()); + const auto results = buffer.FindMatches("find", 0); + ASSERT_EQ(0u, results->CountForTesting()); +} + +TEST_F(FindBufferTest, SelectMultipleTest) { + SetBodyContent("<select multiple><option>find me</option></select>"); + FindBuffer buffer(WholeDocumentRange()); +#if defined(OS_ANDROID) + EXPECT_EQ(0u, buffer.FindMatches("find", 0)->CountForTesting()); +#else + EXPECT_EQ(1u, buffer.FindMatches("find", 0)->CountForTesting()); +#endif // defined(OS_ANDROID) + SetBodyContent("<select size=2><option>find me</option></select>"); + buffer = FindBuffer(WholeDocumentRange()); +#if defined(OS_ANDROID) + EXPECT_EQ(0u, buffer.FindMatches("find", 0)->CountForTesting()); +#else + EXPECT_EQ(1u, buffer.FindMatches("find", 0)->CountForTesting()); +#endif // defined(OS_ANDROID) + SetBodyContent("<select size=1><option>find me</option></select>"); + buffer = FindBuffer(WholeDocumentRange()); + EXPECT_EQ(0u, buffer.FindMatches("find", 0)->CountForTesting()); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_in_page_coordinates.cc b/chromium/third_party/blink/renderer/core/editing/finder/find_in_page_coordinates.cc index 76ecb45df78..c334cd8675f 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_in_page_coordinates.cc +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_in_page_coordinates.cc @@ -33,6 +33,7 @@ #include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/dom/range.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" +#include "third_party/blink/renderer/core/editing/visible_units.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/layout/layout_block.h" @@ -147,8 +148,8 @@ FloatRect FindInPageRectFromRange(const EphemeralRange& range) { if (!baseLayoutObject) return FloatRect(); - return FindInPageRectFromAbsoluteRect( - LayoutObject::AbsoluteBoundingBoxRectForRange(range), baseLayoutObject); + return FindInPageRectFromAbsoluteRect(ComputeTextFloatRect(range), + baseLayoutObject); } } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.cc b/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.cc index 67736f9c97c..80b67e74787 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.cc +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.cc @@ -84,10 +84,16 @@ class FindTaskController::IdleFindTask TimeDelta::FromMillisecondsD(deadline->timeRemaining()); const TimeTicks start_time = CurrentTimeTicks(); - PositionInFlatTree search_start = PositionInFlatTree::FirstPositionInNode( - *controller_->GetLocalFrame()->GetDocument()); - PositionInFlatTree search_end = PositionInFlatTree::LastPositionInNode( - *controller_->GetLocalFrame()->GetDocument()); + Document& document = *controller_->GetLocalFrame()->GetDocument(); + PositionInFlatTree search_start = + PositionInFlatTree::FirstPositionInNode(document); + PositionInFlatTree search_end; + if (document.documentElement() && document.documentElement()->lastChild()) { + search_end = PositionInFlatTree::AfterNode( + *document.documentElement()->lastChild()); + } else { + search_end = PositionInFlatTree::LastPositionInNode(document); + } DCHECK_EQ(search_start.GetDocument(), search_end.GetDocument()); if (Range* resume_from_range = controller_->ResumeFindingFromRange()) { @@ -107,11 +113,17 @@ class FindTaskController::IdleFindTask int match_count = 0; bool full_range_searched = false; PositionInFlatTree next_task_start_position; - do { + + blink::FindOptions find_options = + (options_->forward ? 0 : kBackwards) | + (options_->match_case ? 0 : kCaseInsensitive) | + (options_->find_next ? 0 : kStartInSelection); + + while (search_start != search_end) { // Find in the whole block. - FindBuffer buffer(search_start); + FindBuffer buffer(EphemeralRangeInFlatTree(search_start, search_end)); std::unique_ptr<FindBuffer::Results> match_results = - buffer.FindMatches(search_text_, *options_); + buffer.FindMatches(search_text_, find_options); for (FindBuffer::BufferMatchResult match : *match_results) { const EphemeralRangeInFlatTree ephemeral_match_range = buffer.RangeFromBufferIndex(match.start, @@ -138,7 +150,9 @@ class FindTaskController::IdleFindTask break; } next_task_start_position = search_start; - } while (deadline->timeRemaining() > 0); + if (deadline->timeRemaining() <= 0) + break; + } const TimeDelta time_spent = CurrentTimeTicks() - start_time; UMA_HISTOGRAM_TIMES("WebCore.FindInPage.ScopingTime", @@ -287,4 +301,8 @@ void FindTaskController::Trace(Visitor* visitor) { visitor->Trace(resume_finding_from_range_); } +void FindTaskController::ResetLastFindRequestCompletedWithNoMatches() { + last_find_request_completed_with_no_matches_ = false; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.h b/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.h index 1b99f852f8b..327d0348af8 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.h +++ b/chromium/third_party/blink/renderer/core/editing/finder/find_task_controller.h @@ -76,6 +76,8 @@ class CORE_EXPORT FindTaskController final void Trace(Visitor* visitor); + void ResetLastFindRequestCompletedWithNoMatches(); + private: void RequestIdleFindTask(int identifier, const WebString& search_text, diff --git a/chromium/third_party/blink/renderer/core/editing/finder/text_finder.cc b/chromium/third_party/blink/renderer/core/editing/finder/text_finder.cc index 1f9438e77b9..fc7190494ff 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/text_finder.cc +++ b/chromium/third_party/blink/renderer/core/editing/finder/text_finder.cc @@ -37,6 +37,7 @@ #include "third_party/blink/public/web/web_local_frame_client.h" #include "third_party/blink/public/web/web_view_client.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache_base.h" +#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" #include "third_party/blink/renderer/core/dom/range.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/editing/editor.h" @@ -54,6 +55,7 @@ #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" +#include "third_party/blink/renderer/core/invisible_dom/invisible_dom.h" #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/layout/text_autosizer.h" @@ -67,12 +69,19 @@ namespace blink { TextFinder::FindMatch::FindMatch(Range* range, int ordinal) : range_(range), ordinal_(ordinal) {} -void TextFinder::FindMatch::Trace(blink::Visitor* visitor) { +void TextFinder::FindMatch::Trace(Visitor* visitor) { visitor->Trace(range_); } static void ScrollToVisible(Range* match) { const Node& first_node = *match->FirstNode(); + if (RuntimeEnabledFeatures::InvisibleDOMEnabled() || + RuntimeEnabledFeatures::DisplayLockingEnabled()) { + const EphemeralRangeInFlatTree range(match); + if (InvisibleDOM::ActivateRangeIfNeeded(range) || + DisplayLockUtilities::ActivateFindInPageMatchRangeIfNeeded(range)) + first_node.GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); + } Settings* settings = first_node.GetDocument().GetSettings(); bool smooth_find_enabled = settings ? settings->GetSmoothScrollForFindEnabled() : false; @@ -156,8 +165,7 @@ bool TextFinder::Find(int identifier, ->PageNeedsAutosizing()) { OwnerFrame().LocalRoot()->FrameWidget()->ZoomToFindInPageRect( OwnerFrame().GetFrameView()->ConvertToRootFrame( - EnclosingIntRect(LayoutObject::AbsoluteBoundingBoxRectForRange( - EphemeralRange(active_match_.Get()))))); + ComputeTextRect(EphemeralRange(active_match_.Get())))); } bool was_active_frame = current_active_match_frame_; @@ -204,7 +212,7 @@ bool TextFinder::Find(int identifier, } // We found something, so the result of the previous scoping may be outdated. - last_find_request_completed_with_no_matches_ = false; + find_task_controller_->ResetLastFindRequestCompletedWithNoMatches(); return true; } @@ -320,8 +328,8 @@ void TextFinder::ReportFindInPageResultToAccessibility(int identifier) { if (!active_match_) return; - AXObjectCacheBase* ax_object_cache = ToAXObjectCacheBase( - OwnerFrame().GetFrame()->GetDocument()->ExistingAXObjectCache()); + auto* ax_object_cache = + OwnerFrame().GetFrame()->GetDocument()->ExistingAXObjectCache(); if (!ax_object_cache) return; @@ -607,8 +615,7 @@ int TextFinder::SelectFindMatch(unsigned index, WebRect* selection_rect) { IntRect active_match_rect; IntRect active_match_bounding_box = - EnclosingIntRect(LayoutObject::AbsoluteBoundingBoxRectForRange( - EphemeralRange(active_match_.Get()))); + ComputeTextRect(EphemeralRange(active_match_.Get())); if (!active_match_bounding_box.IsEmpty()) { if (active_match_->FirstNode() && @@ -628,8 +635,7 @@ int TextFinder::SelectFindMatch(unsigned index, WebRect* selection_rect) { // that needs to be merged to a release branch. // https://crbug.com/823365. active_match_bounding_box = - EnclosingIntRect(LayoutObject::AbsoluteBoundingBoxRectForRange( - EphemeralRange(active_match_.Get()))); + ComputeTextRect(EphemeralRange(active_match_.Get())); } // Zoom to the active match. @@ -661,7 +667,6 @@ TextFinder::TextFinder(WebLocalFrameImpl& owner_frame) find_match_markers_version_(0), should_locate_active_rect_(false), scoping_in_progress_(false), - last_find_request_completed_with_no_matches_(false), find_match_rects_are_valid_(false) {} TextFinder::~TextFinder() = default; @@ -712,7 +717,7 @@ void TextFinder::InvalidatePaintForTickmarks() { OwnerFrame().GetFrame()->ContentLayoutObject()->InvalidatePaintForTickmarks(); } -void TextFinder::Trace(blink::Visitor* visitor) { +void TextFinder::Trace(Visitor* visitor) { visitor->Trace(owner_frame_); visitor->Trace(find_task_controller_); visitor->Trace(active_match_); diff --git a/chromium/third_party/blink/renderer/core/editing/finder/text_finder.h b/chromium/third_party/blink/renderer/core/editing/finder/text_finder.h index 3c3b9ab0001..38a250a2ab0 100644 --- a/chromium/third_party/blink/renderer/core/editing/finder/text_finder.h +++ b/chromium/third_party/blink/renderer/core/editing/finder/text_finder.h @@ -130,7 +130,7 @@ class CORE_EXPORT TextFinder final public: FindMatch(Range*, int ordinal); - void Trace(blink::Visitor*); + void Trace(Visitor*); Member<Range> range_; @@ -142,7 +142,7 @@ class CORE_EXPORT TextFinder final FloatRect rect_; }; - void Trace(blink::Visitor*); + void Trace(Visitor*); private: // Notifies the delegate about a new selection rect. @@ -240,10 +240,6 @@ class CORE_EXPORT TextFinder final // Keeps track of whether there is an scoping effort ongoing in the frame. bool scoping_in_progress_; - // Keeps track of whether the last find request completed its scoping effort - // without finding any matches in this frame. - bool last_find_request_completed_with_no_matches_; - // Determines if the rects in the find-in-page matches cache of this frame // are invalid and should be recomputed. bool find_match_rects_are_valid_; @@ -253,6 +249,6 @@ class CORE_EXPORT TextFinder final } // namespace blink -WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::TextFinder::FindMatch); +WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::TextFinder::FindMatch) #endif // THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_FINDER_TEXT_FINDER_H_ |