blob: a16941dbc6d53d90322c84b6186ac2e02d29152a [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/editing/finder/text_finder.h"
#include "components/ukm/test_ukm_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_list.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/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/finder/find_in_page_coordinates.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/layout/text_autosizer.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
class TextFinderTest : public testing::Test {
protected:
TextFinderTest() {
web_view_helper_.Initialize();
WebLocalFrameImpl& frame_impl = *web_view_helper_.LocalMainFrame();
frame_impl.ViewImpl()->MainFrameViewWidget()->Resize(gfx::Size(640, 480));
frame_impl.ViewImpl()->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
document_ = static_cast<Document*>(frame_impl.GetDocument());
text_finder_ = &frame_impl.EnsureTextFinder();
}
Document& GetDocument() const;
TextFinder& GetTextFinder() const;
v8::Local<v8::Value> EvalJs(const std::string& script);
static gfx::RectF FindInPageRect(Node* start_container,
int start_offset,
Node* end_container,
int end_offset);
private:
frame_test_helpers::WebViewHelper web_view_helper_;
Persistent<Document> document_;
Persistent<TextFinder> text_finder_;
};
class TextFinderSimTest : public SimTest {
protected:
TextFinder& GetTextFinder() {
return WebLocalFrameImpl::FromFrame(GetDocument().GetFrame())
->EnsureTextFinder();
}
};
v8::Local<v8::Value> TextFinderTest::EvalJs(const std::string& script) {
return ClassicScript::CreateUnspecifiedScript(
ScriptSourceCode(script.c_str()))
->RunScriptAndReturnValue(GetDocument().domWindow());
}
Document& TextFinderTest::GetDocument() const {
return *document_;
}
TextFinder& TextFinderTest::GetTextFinder() const {
return *text_finder_;
}
gfx::RectF TextFinderTest::FindInPageRect(Node* start_container,
int start_offset,
Node* end_container,
int end_offset) {
const Position start_position(start_container, start_offset);
const Position end_position(end_container, end_offset);
const EphemeralRange range(start_position, end_position);
return gfx::RectF(FindInPageRectFromRange(range));
}
TEST_F(TextFinderTest, FindTextSimple) {
GetDocument().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
bool wrap_within_frame = true;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
Range* active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(4u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(10u, active_match->endOffset());
find_options->new_session = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(14u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(20u, active_match->endOffset());
// Should wrap to the first match.
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(4u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(10u, active_match->endOffset());
// Search in the reverse order.
identifier = 1;
find_options = mojom::blink::FindOptions::New();
find_options->forward = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(14u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(20u, active_match->endOffset());
find_options->new_session = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(4u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(10u, active_match->endOffset());
// Wrap to the first match (last occurence in the document).
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_node, active_match->startContainer());
EXPECT_EQ(14u, active_match->startOffset());
EXPECT_EQ(text_node, active_match->endContainer());
EXPECT_EQ(20u, active_match->endOffset());
}
TEST_F(TextFinderTest, FindTextAutosizing) {
GetDocument().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
bool wrap_within_frame = true;
// Set viewport scale to 20 in order to simulate zoom-in
GetDocument().GetPage()->SetDefaultPageScaleLimits(1, 20);
GetDocument().GetPage()->SetPageScaleFactor(20);
VisualViewport& visual_viewport =
GetDocument().GetPage()->GetVisualViewport();
// Enforce autosizing
GetDocument().GetSettings()->SetTextAutosizingEnabled(true);
GetDocument().GetSettings()->SetTextAutosizingWindowSizeOverride(
IntSize(20, 20));
GetDocument().GetTextAutosizer()->UpdatePageInfo();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
// In case of autosizing, scale _should_ change
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
ASSERT_TRUE(GetTextFinder().ActiveMatch());
ASSERT_EQ(1, visual_viewport.Scale()); // in this case to 1
// Disable autosizing and reset scale to 20
visual_viewport.SetScale(20);
GetDocument().GetSettings()->SetTextAutosizingEnabled(false);
GetDocument().GetTextAutosizer()->UpdatePageInfo();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
ASSERT_TRUE(GetTextFinder().ActiveMatch());
ASSERT_EQ(20, visual_viewport.Scale());
}
TEST_F(TextFinderTest, FindTextNotFound) {
GetDocument().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("Boo"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
bool wrap_within_frame = true;
EXPECT_FALSE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
EXPECT_FALSE(GetTextFinder().ActiveMatch());
}
TEST_F(TextFinderTest, FindTextInShadowDOM) {
GetDocument().body()->setInnerHTML("<b>FOO</b><i slot='bar'>foo</i>");
ShadowRoot& shadow_root =
GetDocument().body()->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.setInnerHTML("<slot name='bar'></slot><u>Foo</u><slot></slot>");
Node* text_in_b_element = GetDocument().body()->firstChild()->firstChild();
Node* text_in_i_element = GetDocument().body()->lastChild()->firstChild();
Node* text_in_u_element = shadow_root.childNodes()->item(1)->firstChild();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("foo"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
bool wrap_within_frame = true;
// TextIterator currently returns the matches in the flat treeorder, so
// in this case the matches will be returned in the order of
// <i> -> <u> -> <b>.
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
Range* active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_i_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_i_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
find_options->new_session = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_u_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_u_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_b_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_b_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
// Should wrap to the first match.
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_i_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_i_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
// Fresh search in the reverse order.
identifier = 1;
find_options = mojom::blink::FindOptions::New();
find_options->forward = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_b_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_b_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
find_options->new_session = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_u_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_u_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_i_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_i_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
// And wrap.
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_EQ(text_in_b_element, active_match->startContainer());
EXPECT_EQ(0u, active_match->startOffset());
EXPECT_EQ(text_in_b_element, active_match->endContainer());
EXPECT_EQ(3u, active_match->endOffset());
}
TEST_F(TextFinderTest, ScopeTextMatchesSimple) {
GetDocument().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
EXPECT_EQ(2, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(2u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 4, text_node, 10), match_rects[0]);
EXPECT_EQ(FindInPageRect(text_node, 14, text_node, 20), match_rects[1]);
// Modify the document size and ensure the cached match rects are recomputed
// to reflect the updated layout.
GetDocument().body()->setAttribute(html_names::kStyleAttr, "margin: 2000px");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(2u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 4, text_node, 10), match_rects[0]);
EXPECT_EQ(FindInPageRect(text_node, 14, text_node, 20), match_rects[1]);
}
TEST_F(TextFinderTest, ScopeTextMatchesRepeated) {
GetDocument().body()->setInnerHTML("XXXXFindMeYYYYfindmeZZZZ");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text1(String("XFindMe"));
WebString search_text2(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text1,
*find_options);
GetTextFinder().StartScopingStringMatches(identifier, search_text2,
*find_options);
// Only searchText2 should be highlighted.
EXPECT_EQ(2, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(2u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 4, text_node, 10), match_rects[0]);
EXPECT_EQ(FindInPageRect(text_node, 14, text_node, 20), match_rects[1]);
}
TEST_F(TextFinderTest, ScopeTextMatchesWithShadowDOM) {
GetDocument().body()->setInnerHTML("<b>FOO</b><i slot='bar'>foo</i>");
ShadowRoot& shadow_root =
GetDocument().body()->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.setInnerHTML("<slot name='bar'></slot><u>Foo</u><slot></slot>");
Node* text_in_b_element = GetDocument().body()->firstChild()->firstChild();
Node* text_in_i_element = GetDocument().body()->lastChild()->firstChild();
Node* text_in_u_element = shadow_root.childNodes()->item(1)->firstChild();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("fOO"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
// TextIterator currently returns the matches in the flat tree order,
// so in this case the matches will be returned in the order of
// <i> -> <u> -> <b>.
EXPECT_EQ(3, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(3u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_in_i_element, 0, text_in_i_element, 3),
match_rects[0]);
EXPECT_EQ(FindInPageRect(text_in_u_element, 0, text_in_u_element, 3),
match_rects[1]);
EXPECT_EQ(FindInPageRect(text_in_b_element, 0, text_in_b_element, 3),
match_rects[2]);
}
TEST_F(TextFinderTest, ScopeRepeatPatternTextMatches) {
GetDocument().body()->setInnerHTML("ab ab ab ab ab");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text(String("ab ab"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
EXPECT_EQ(2, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(2u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 0, text_node, 5), match_rects[0]);
EXPECT_EQ(FindInPageRect(text_node, 6, text_node, 11), match_rects[1]);
}
TEST_F(TextFinderTest, OverlappingMatches) {
GetDocument().body()->setInnerHTML("aababaa");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text(String("aba"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
// We shouldn't find overlapped matches.
EXPECT_EQ(1, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(1u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 1, text_node, 4), match_rects[0]);
}
TEST_F(TextFinderTest, SequentialMatches) {
GetDocument().body()->setInnerHTML("ababab");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Node* text_node = GetDocument().body()->firstChild();
int identifier = 0;
WebString search_text(String("ab"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
EXPECT_EQ(3, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(3u, match_rects.size());
EXPECT_EQ(FindInPageRect(text_node, 0, text_node, 2), match_rects[0]);
EXPECT_EQ(FindInPageRect(text_node, 2, text_node, 4), match_rects[1]);
EXPECT_EQ(FindInPageRect(text_node, 4, text_node, 6), match_rects[2]);
}
TEST_F(TextFinderTest, FindTextJavaScriptUpdatesDOM) {
GetDocument().body()->setInnerHTML("<b>XXXXFindMeYYYY</b><i></i>");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
bool wrap_within_frame = true;
bool active_now;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
find_options->new_session = false;
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame, &active_now));
EXPECT_TRUE(active_now);
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame, &active_now));
EXPECT_TRUE(active_now);
// Add new text to DOM and try FindNext.
auto* i_element = To<Element>(GetDocument().body()->lastChild());
ASSERT_TRUE(i_element);
i_element->setInnerHTML("ZZFindMe");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame, &active_now));
Range* active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_FALSE(active_now);
EXPECT_EQ(2u, active_match->startOffset());
EXPECT_EQ(8u, active_match->endOffset());
// Restart full search and check that added text is found.
find_options->new_session = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().CancelPendingScopingEffort();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
EXPECT_EQ(2, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(2u, match_rects.size());
Node* text_in_b_element = GetDocument().body()->firstChild()->firstChild();
Node* text_in_i_element = GetDocument().body()->lastChild()->firstChild();
EXPECT_EQ(FindInPageRect(text_in_b_element, 4, text_in_b_element, 10),
match_rects[0]);
EXPECT_EQ(FindInPageRect(text_in_i_element, 2, text_in_i_element, 8),
match_rects[1]);
}
TEST_F(TextFinderTest, FindTextJavaScriptUpdatesDOMAfterNoMatches) {
GetDocument().body()->setInnerHTML("<b>XXXXYYYY</b><i></i>");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("FindMe"));
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
bool wrap_within_frame = true;
bool active_now = false;
GetTextFinder().ResetMatchCount();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
find_options->new_session = false;
ASSERT_FALSE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame, &active_now));
EXPECT_FALSE(active_now);
// Add new text to DOM and try FindNext.
auto* i_element = To<Element>(GetDocument().body()->lastChild());
ASSERT_TRUE(i_element);
i_element->setInnerHTML("ZZFindMe");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
ASSERT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame, &active_now));
Range* active_match = GetTextFinder().ActiveMatch();
ASSERT_TRUE(active_match);
EXPECT_FALSE(active_now);
EXPECT_EQ(2u, active_match->startOffset());
EXPECT_EQ(8u, active_match->endOffset());
// Restart full search and check that added text is found.
find_options->new_session = true;
GetTextFinder().ResetMatchCount();
GetTextFinder().CancelPendingScopingEffort();
GetTextFinder().StartScopingStringMatches(identifier, search_text,
*find_options);
EXPECT_EQ(1, GetTextFinder().TotalMatchCount());
WebVector<gfx::RectF> match_rects = GetTextFinder().FindMatchRects();
ASSERT_EQ(1u, match_rects.size());
Node* text_in_i_element = GetDocument().body()->lastChild()->firstChild();
EXPECT_EQ(FindInPageRect(text_in_i_element, 2, text_in_i_element, 8),
match_rects[0]);
}
TEST_F(TextFinderTest, ScopeWithTimeouts) {
// Make a long string.
String search_pattern("abc");
StringBuilder text;
// Make 4 substrings "abc" in text.
for (int i = 0; i < 100; ++i) {
if (i == 1 || i == 10 || i == 50 || i == 90) {
text.Append(search_pattern);
} else {
text.Append('a');
}
}
GetDocument().body()->setInnerHTML(text.ToString());
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
auto find_options =
mojom::blink::FindOptions::New(); // Default + add testing flag.
find_options->run_synchronously_for_testing = true;
GetTextFinder().ResetMatchCount();
// There will be only one iteration before timeout, because increment
// of the TimeProxyPlatform timer is greater than timeout threshold.
GetTextFinder().StartScopingStringMatches(identifier, search_pattern,
*find_options);
EXPECT_EQ(4, GetTextFinder().TotalMatchCount());
}
TEST_F(TextFinderTest, BeforeMatchEvent) {
V8TestingScope v8_testing_scope;
EvalJs(R"(
const spacer = document.createElement('div');
spacer.style.height = '2000px';
document.body.appendChild(spacer);
const foo = document.createElement('div');
foo.textContent = 'foo';
document.body.appendChild(foo);
window.beforematchFiredOnFoo = false;
foo.addEventListener('beforematch', () => {
window.beforematchFiredOnFoo = true;
});
const bar = document.createElement('div');
bar.textContent = 'bar';
document.body.appendChild(bar);
window.beforematchFiredOnBar = false;
bar.addEventListener('beforematch', () => {
window.YOffsetOnBeforematch = window.pageYOffset;
window.beforematchFiredOnBar = true;
});
)");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
auto find_options = mojom::blink::FindOptions::New();
find_options->run_synchronously_for_testing = true;
GetTextFinder().Find(/*identifier=*/0, WebString(String("bar")),
*find_options, /*wrap_within_frame=*/false);
v8::Local<v8::Value> beforematch_fired_on_foo =
EvalJs("window.beforematchFiredOnFoo");
ASSERT_TRUE(beforematch_fired_on_foo->IsBoolean());
EXPECT_FALSE(
beforematch_fired_on_foo->ToBoolean(v8::Isolate::GetCurrent())->Value());
v8::Local<v8::Value> beforematch_fired_on_bar =
EvalJs("window.beforematchFiredOnBar");
ASSERT_TRUE(beforematch_fired_on_bar->IsBoolean());
EXPECT_TRUE(
beforematch_fired_on_bar->ToBoolean(v8::Isolate::GetCurrent())->Value());
// Scrolling should occur after the beforematch event.
v8::Local<v8::Context> context =
v8_testing_scope.GetScriptState()->GetContext();
v8::Local<v8::Value> beforematch_y_offset =
EvalJs("window.YOffsetOnBeforematch");
ASSERT_TRUE(beforematch_y_offset->IsNumber());
EXPECT_TRUE(
beforematch_y_offset->ToNumber(context).ToLocalChecked()->Value() == 0);
}
TEST_F(TextFinderTest, BeforeMatchEventRemoveElement) {
V8TestingScope v8_testing_scope;
EvalJs(R"(
const spacer = document.createElement('div');
spacer.style.height = '2000px';
document.body.appendChild(spacer);
const foo = document.createElement('div');
foo.textContent = 'foo';
document.body.appendChild(foo);
window.beforematchFiredOnFoo = false;
foo.addEventListener('beforematch', () => {
foo.remove();
window.beforematchFiredOnFoo = true;
});
)");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
auto find_options = mojom::blink::FindOptions::New();
find_options->run_synchronously_for_testing = true;
GetTextFinder().Find(/*identifier=*/0, WebString(String("foo")),
*find_options, /*wrap_within_frame=*/false);
v8::Local<v8::Value> beforematch_fired_on_foo =
EvalJs("window.beforematchFiredOnFoo");
ASSERT_TRUE(beforematch_fired_on_foo->IsBoolean());
EXPECT_TRUE(
beforematch_fired_on_foo->ToBoolean(v8::Isolate::GetCurrent())->Value());
// TODO(jarhar): Update this test to include checks for scrolling behavior
// once we decide what the behavior should be. Right now it is just here to
// make sure we avoid a renderer crash due to the detached element.
}
// TODO(jarhar): Write more tests here once we decide on a behavior here:
// https://github.com/WICG/display-locking/issues/150
TEST_F(TextFinderSimTest, BeforeMatchEventAsyncExpandHighlight) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
.hidden {
content-visibility: hidden-matchable;
}
</style>
<div id=hiddenid class=hidden>hidden</div>
<script>
hiddenid.addEventListener('beforematch', () => {
requestAnimationFrame(() => {
hiddenid.classList.remove('hidden');
}, 0);
});
</script>
)HTML");
Compositor().BeginFrame();
auto forced_activatable_locks = GetDocument()
.GetDisplayLockDocumentState()
.GetScopedForceActivatableLocks();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
GetTextFinder().Find(/*identifier=*/0, WebString(String("hidden")),
*mojom::blink::FindOptions::New(),
/*wrap_within_frame=*/false);
Compositor().BeginFrame();
Compositor().BeginFrame();
HeapVector<Member<DocumentMarker>> markers =
GetDocument().Markers().Markers();
ASSERT_EQ(markers.size(), 1u);
DocumentMarker* marker = markers[0];
EXPECT_TRUE(marker->GetType() == DocumentMarker::kTextMatch);
}
TEST_F(TextFinderSimTest, BeforeMatchExpandedHiddenMatchableUkm) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
.hidden {
content-visibility: hidden-matchable;
}
</style>
<div id=hiddenid class=hidden>hidden</div>
<script>
hiddenid.addEventListener('beforematch', () => {
requestAnimationFrame(() => {
hiddenid.classList.remove('hidden');
}, 0);
});
</script>
)HTML");
Compositor().BeginFrame();
GetDocument().ukm_recorder_ = std::make_unique<ukm::TestUkmRecorder>();
auto* recorder =
static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
EXPECT_EQ(recorder->entries_count(), 0u);
auto forced_activatable_locks = GetDocument()
.GetDisplayLockDocumentState()
.GetScopedForceActivatableLocks();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
GetTextFinder().Find(/*identifier=*/0, WebString(String("hidden")),
*mojom::blink::FindOptions::New(),
/*wrap_within_frame=*/false);
Compositor().BeginFrame();
Compositor().BeginFrame();
auto entries = recorder->GetEntriesByName("Blink.FindInPage");
ASSERT_EQ(entries.size(), 1u);
EXPECT_TRUE(ukm::TestUkmRecorder::EntryHasMetric(
entries[0], "BeforematchExpandedHiddenMatchable"));
}
TEST_F(TextFinderSimTest, BeforeMatchExpandedHiddenMatchableUkmNoHandler) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
.hidden {
content-visibility: hidden-matchable;
}
</style>
<div id=hiddenid class=hidden>hidden</div>
)HTML");
Compositor().BeginFrame();
GetDocument().ukm_recorder_ = std::make_unique<ukm::TestUkmRecorder>();
auto* recorder =
static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
EXPECT_EQ(recorder->entries_count(), 0u);
auto forced_activatable_locks = GetDocument()
.GetDisplayLockDocumentState()
.GetScopedForceActivatableLocks();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
GetTextFinder().Find(/*identifier=*/0, WebString(String("hidden")),
*mojom::blink::FindOptions::New(),
/*wrap_within_frame=*/false);
Compositor().BeginFrame();
Compositor().BeginFrame();
auto entries = recorder->GetEntriesByName("Blink.FindInPage");
EXPECT_EQ(entries.size(), 0u);
}
TEST_F(TextFinderSimTest, BeforeMatchExpandedHiddenMatchableUseCounter) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
.hidden {
content-visibility: hidden-matchable;
}
</style>
<div id=hiddenid class=hidden>hidden</div>
<script>
hiddenid.addEventListener('beforematch', () => {
requestAnimationFrame(() => {
hiddenid.classList.remove('hidden');
}, 0);
});
</script>
)HTML");
Compositor().BeginFrame();
auto forced_activatable_locks = GetDocument()
.GetDisplayLockDocumentState()
.GetScopedForceActivatableLocks();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
GetTextFinder().Find(/*identifier=*/0, WebString(String("hidden")),
*mojom::blink::FindOptions::New(),
/*wrap_within_frame=*/false);
Compositor().BeginFrame();
Compositor().BeginFrame();
EXPECT_TRUE(GetDocument().IsUseCounted(
WebFeature::kBeforematchRevealedHiddenMatchable));
}
TEST_F(TextFinderSimTest,
BeforeMatchExpandedHiddenMatchableUseCounterNoHandler) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>
.hidden {
content-visibility: hidden-matchable;
}
</style>
<div id=hiddenid class=hidden>hidden</div>
)HTML");
Compositor().BeginFrame();
auto forced_activatable_locks = GetDocument()
.GetDisplayLockDocumentState()
.GetScopedForceActivatableLocks();
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kFindInPage);
GetTextFinder().Find(/*identifier=*/0, WebString(String("hidden")),
*mojom::blink::FindOptions::New(),
/*wrap_within_frame=*/false);
Compositor().BeginFrame();
Compositor().BeginFrame();
EXPECT_FALSE(GetDocument().IsUseCounted(
WebFeature::kBeforematchRevealedHiddenMatchable));
}
TEST_F(TextFinderTest, FindTextAcrossCommentNode) {
GetDocument().body()->setInnerHTML(
"<span>abc</span><!--comment--><span>def</span>");
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kTest);
int identifier = 0;
WebString search_text(String("abcdef"));
auto find_options = mojom::blink::FindOptions::New();
find_options->run_synchronously_for_testing = true;
bool wrap_within_frame = true;
EXPECT_TRUE(GetTextFinder().Find(identifier, search_text, *find_options,
wrap_within_frame));
EXPECT_TRUE(GetTextFinder().ActiveMatch());
}
} // namespace blink