#include "base/containers/span.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/shared_highlighting/core/common/shared_highlighting_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_menu_source_type.h"
#include "third_party/blink/public/public_buildflags.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_font_face_descriptors.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_mouse_event_init.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_arraybuffer_arraybufferview_string.h"
#include "third_party/blink/renderer/core/annotation/annotation_agent_container_impl.h"
#include "third_party/blink/renderer/core/annotation/annotation_agent_impl.h"
#include "third_party/blink/renderer/core/css/font_face_set_document.h"
#include "third_party/blink/renderer/core/dom/element.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/frame_selection.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/fragment_directive/text_fragment_finder.h"
#include "third_party/blink/renderer/core/fragment_directive/text_fragment_test_util.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.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/frame/location.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/page/context_menu_controller.h"
#include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/platform/scheduler/public/main_thread_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#if BUILDFLAG(ENABLE_UNHANDLED_TAP)
#include "third_party/blink/public/mojom/unhandled_tap_notifier/unhandled_tap_notifier.mojom-blink.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#endif
namespace blink {
namespace {
RunPendingTasks;
class TextFragmentAnchorTestController : public TextFragmentAnchorTestBase { … };
class TextFragmentAnchorTest : public TextFragmentAnchorTestController { … };
TEST_F(TextFragmentAnchorTest, BasicSmokeTest) { … }
TEST_F(TextFragmentAnchorTest, EmptyText) { … }
TEST_F(TextFragmentAnchorTest, NonMatchingString) { … }
TEST_F(TextFragmentAnchorTest, MultipleMatches) { … }
TEST_F(TextFragmentAnchorTest, NestedBlocks) { … }
TEST_F(TextFragmentAnchorTest, MultipleTextFragments) { … }
TEST_F(TextFragmentAnchorTest, FirstTextFragmentNotFound) { … }
TEST_F(TextFragmentAnchorTest, OnlyFirstTextFragmentFound) { … }
TEST_F(TextFragmentAnchorTest, MultipleNonMatchingStrings) { … }
TEST_F(TextFragmentAnchorTest, SameElementTextRange) { … }
TEST_F(TextFragmentAnchorTest, NeighboringElementTextRange) { … }
TEST_F(TextFragmentAnchorTest, DifferentDepthElementTextRange) { … }
TEST_F(TextFragmentAnchorTest, TextRangeEndTextNotFound) { … }
TEST_F(TextFragmentAnchorTest, MultipleTextRanges) { … }
TEST_F(TextFragmentAnchorTest, DistantElementTextRange) { … }
TEST_F(TextFragmentAnchorTest, TextRangeWithContext) { … }
TEST_F(TextFragmentAnchorTest, PrefixNotFound) { … }
TEST_F(TextFragmentAnchorTest, SuffixNotFound) { … }
TEST_F(TextFragmentAnchorTest, TextRangeWithCrossElementContext) { … }
TEST_F(TextFragmentAnchorTest, CrossElementAndWhitespaceContext) { … }
TEST_F(TextFragmentAnchorTest, CrossEmptySiblingAndParentElementContext) { … }
TEST_F(TextFragmentAnchorTest, DistantElementContext) { … }
TEST_F(TextFragmentAnchorTest, OneContextTerm) { … }
class TextFragmentAnchorScrollTest
: public TextFragmentAnchorTest,
public testing::WithParamInterface<mojom::blink::ScrollType> { … };
INSTANTIATE_TEST_SUITE_P(…);
TEST_P(TextFragmentAnchorScrollTest, ScrollCancelled) { … }
TEST_P(TextFragmentAnchorScrollTest, DontDismissTextHighlightOnUserScroll) { … }
TEST_F(TextFragmentAnchorTest, DisabledInIframes) { … }
TEST_F(TextFragmentAnchorTest, DisabledInWindowOpen) { … }
TEST_F(TextFragmentAnchorTest, DisabledInSamePageNavigation) { … }
TEST_F(TextFragmentAnchorTest, CaseInsensitive) { … }
TEST_F(TextFragmentAnchorTest, TargetStaysInView) { … }
TEST_F(TextFragmentAnchorTest, OverlappingTextRanges) { … }
TEST_F(TextFragmentAnchorTest, SpaceMatchesNbsp) { … }
TEST_F(TextFragmentAnchorTest, CSSTextTransform) { … }
TEST_F(TextFragmentAnchorTest, NoMatchFoundFallsBackToElementFragment) { … }
TEST_F(TextFragmentAnchorTest, CheckForWordBoundary) { … }
TEST_F(TextFragmentAnchorTest, CheckForWordBoundaryWithContext) { … }
TEST_F(TextFragmentAnchorTest, CheckForWordBoundaryWithPartialWord) { … }
TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithClick) { … }
TEST_F(TextFragmentAnchorTest, DontDismissTextHighlightWithClick) { … }
TEST_F(TextFragmentAnchorTest, KeepsTextHighlightWithTap) { … }
TEST_F(TextFragmentAnchorTest, DontDismissTextHighlightWithTap) { … }
TEST_F(TextFragmentAnchorTest, KeepsTextHighlightOutOfView) { … }
TEST_F(TextFragmentAnchorTest, KeepsTextHighlightInView) { … }
TEST_F(TextFragmentAnchorTest, FragmentDirectiveDelimiter) { … }
TEST_F(TextFragmentAnchorTest, FragmentDirectiveDelimiterWithElementFragment) { … }
TEST_F(TextFragmentAnchorTest, IdFragmentWithFragmentDirective) { … }
TEST_F(TextFragmentAnchorTest, TextDirectiveInSvg) { … }
TEST_F(TextFragmentAnchorTest, DISABLED_HighlightOnReload) { … }
TEST_F(TextFragmentAnchorTest, NonTextDirectives) { … }
TEST_F(TextFragmentAnchorTest, CssTarget) { … }
TEST_F(TextFragmentAnchorTest, PageVisibility) { … }
TEST_F(TextFragmentAnchorTest, ManualRestorationDoesntBlockFragment) { … }
TEST_F(TextFragmentAnchorTest, ReplaceStateDoesntBlockFragment) { … }
TEST_F(TextFragmentAnchorTest, MatchAcrossCommentNode) { … }
TEST_F(TextFragmentAnchorTest, SamePrefixAndText) { … }
TEST_F(TextFragmentAnchorTest, IsInSameUninterruptedBlock_OneTextNode) { … }
TEST_F(TextFragmentAnchorTest,
IsInSameUninterruptedBlock_NonBlockInterruption) { … }
TEST_F(TextFragmentAnchorTest, IsInSameUninterruptedBlock_BlockInterruption) { … }
TEST_F(TextFragmentAnchorTest, OpenedFromHighlightDoesNotSelectAdditionalText) { … }
TEST_F(TextFragmentAnchorTest, ShouldOpenContextMenuOnTap) { … }
#if BUILDFLAG(ENABLE_UNHANDLED_TAP)
class MockUnhandledTapNotifierImpl : public mojom::blink::UnhandledTapNotifier {
public:
MockUnhandledTapNotifierImpl() = default;
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::blink::UnhandledTapNotifier>(
std::move(handle)));
}
void ShowUnhandledTapUIIfNeeded(
mojom::blink::UnhandledTapInfoPtr unhandled_tap_info) override {
was_unhandled_tap_ = true;
}
bool WasUnhandledTap() const { return was_unhandled_tap_; }
bool ReceiverIsBound() const { return receiver_.is_bound(); }
void Reset() {
was_unhandled_tap_ = false;
receiver_.reset();
}
private:
bool was_unhandled_tap_ = false;
mojo::Receiver<mojom::blink::UnhandledTapNotifier> receiver_{this};
};
#endif
#if BUILDFLAG(ENABLE_UNHANDLED_TAP)
TEST_F(TextFragmentAnchorTest,
ShouldNotRequestUnhandledTapNotifierWhenTapOnTextFragment) {
LoadAhem();
SimRequest request(
"https://example.com/"
"test.html#:~:text=this%20is%20a%20test%20page",
"text/html");
LoadURL(
"https://example.com/"
"test.html#:~:text=this%20is%20a%20test%20page");
request.Complete(R"HTML(
<!DOCTYPE html>
<style>p { font: 10px/1 Ahem; }</style>
<p id="first">This is a test page</p>
<p id="two">Second test page two</p>
)HTML");
RunUntilTextFragmentFinalization();
MockUnhandledTapNotifierImpl mock_notifier;
GetDocument().GetFrame()->GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::UnhandledTapNotifier::Name_,
WTF::BindRepeating(&MockUnhandledTapNotifierImpl::Bind,
WTF::Unretained(&mock_notifier)));
Range* range = Range::Create(GetDocument());
range->setStart(GetDocument().getElementById(AtomicString("first")), 0,
IGNORE_EXCEPTION_FOR_TESTING);
range->setEnd(GetDocument().getElementById(AtomicString("first")), 1,
IGNORE_EXCEPTION_FOR_TESTING);
ASSERT_EQ("This is a test page", range->GetText());
mock_notifier.Reset();
gfx::Point tap_point = range->BoundingBox().CenterPoint();
SimulateTap(tap_point.x(), tap_point.y());
base::RunLoop().RunUntilIdle();
if (RuntimeEnabledFeatures::TextFragmentTapOpensContextMenuEnabled()) {
EXPECT_FALSE(mock_notifier.WasUnhandledTap());
EXPECT_FALSE(mock_notifier.ReceiverIsBound());
} else {
EXPECT_TRUE(mock_notifier.WasUnhandledTap());
EXPECT_TRUE(mock_notifier.ReceiverIsBound());
}
range->setStart(GetDocument().getElementById(AtomicString("two")), 0,
IGNORE_EXCEPTION_FOR_TESTING);
range->setEndAfter(GetDocument().getElementById(AtomicString("two")),
IGNORE_EXCEPTION_FOR_TESTING);
ASSERT_EQ("Second test page two", range->GetText());
mock_notifier.Reset();
tap_point = range->BoundingBox().CenterPoint();
SimulateTap(tap_point.x(), tap_point.y());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(mock_notifier.WasUnhandledTap());
EXPECT_TRUE(mock_notifier.ReceiverIsBound());
}
#endif
TEST_F(TextFragmentAnchorTest, TapOpeningContextMenuWithDirtyLifecycleNoCrash) { … }
TEST_F(TextFragmentAnchorTest, InitialMatchingIsCollapsedCrash) { … }
TEST_F(TextFragmentAnchorTest, InitialMatchPendingBecomesCollapsed) { … }
class TextFragmentAnchorPostLoadTest : public TextFragmentAnchorTestController { … };
TEST_F(TextFragmentAnchorPostLoadTest, ContentAddedPostLoad) { … }
TEST_F(TextFragmentAnchorPostLoadTest, HiddenAfterFoundPostLoad) { … }
TEST_F(TextFragmentAnchorPostLoadTest, PostLoadSearchEndsWithoutDomMutation) { … }
TEST_F(TextFragmentAnchorPostLoadTest, PostLoadSearchTimesOut) { … }
}
}