diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/core/page/scrolling')
14 files changed, 621 insertions, 135 deletions
diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc index a59dcfae2cb..bd4499fa183 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor_test.cc @@ -231,34 +231,20 @@ TEST_F(ElementFragmentAnchorTest, AnchorRemovedBeforeBeginFrameCrash) { "text/css"); LoadURL("https://example.com/test.html#anchor"); - if (!RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled()) { - main_resource.Complete(R"HTML( + main_resource.Complete(R"HTML( <!DOCTYPE html> <link rel="stylesheet" type="text/css" href="sheet.css"> <div style="height: 1000px;"></div> <input id="anchor">Bottom of the page</input> )HTML"); - // We're still waiting on the stylesheet to load so the load event shouldn't - // yet dispatch and parsing is deferred. This will install the anchor. - ASSERT_FALSE(GetDocument().IsLoadCompleted()); - ASSERT_TRUE(GetDocument().View()->GetFragmentAnchor()); - ASSERT_TRUE(static_cast<ElementFragmentAnchor*>( - GetDocument().View()->GetFragmentAnchor()) - ->anchor_node_); - } else { - main_resource.Complete(R"HTML( - <!DOCTYPE html> - <div style="height: 1000px;"></div> - <input id="anchor">Bottom of the page</input> - <link rel="stylesheet" type="text/css" href="sheet.css"> - )HTML"); - - // We're still waiting on the stylesheet to load so the load event shouldn't - // yet dispatch and parsing is deferred. This will install the anchor. - ASSERT_FALSE(GetDocument().IsLoadCompleted()); - ASSERT_FALSE(GetDocument().View()->GetFragmentAnchor()); - } + // We're still waiting on the stylesheet to load so the load event shouldn't + // yet dispatch and parsing is deferred. This will install the anchor. + ASSERT_FALSE(GetDocument().IsLoadCompleted()); + ASSERT_TRUE(GetDocument().View()->GetFragmentAnchor()); + ASSERT_TRUE(static_cast<ElementFragmentAnchor*>( + GetDocument().View()->GetFragmentAnchor()) + ->anchor_node_); // Remove the fragment anchor from the DOM and perform GC. GetDocument().getElementById("anchor")->remove(); @@ -271,18 +257,12 @@ TEST_F(ElementFragmentAnchorTest, AnchorRemovedBeforeBeginFrameCrash) { css_resource.Complete(""); test::RunPendingTasks(); - if (!RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled()) { - // We should still have a fragment anchor but its node pointer should be - // gone since it's a WeakMember. - ASSERT_TRUE(GetDocument().View()->GetFragmentAnchor()); - ASSERT_FALSE(static_cast<ElementFragmentAnchor*>( - GetDocument().View()->GetFragmentAnchor()) - ->anchor_node_); - } else { - // The fragment shouldn't have installed since the targeted element was - // removed. - ASSERT_FALSE(GetDocument().View()->GetFragmentAnchor()); - } + // We should still have a fragment anchor but its node pointer should be + // gone since it's a WeakMember. + ASSERT_TRUE(GetDocument().View()->GetFragmentAnchor()); + ASSERT_FALSE(static_cast<ElementFragmentAnchor*>( + GetDocument().View()->GetFragmentAnchor()) + ->anchor_node_); // We'd normally focus the fragment during BeginFrame. Make sure we don't // crash since it's been GC'd. diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc index 8d90be489a7..579103d4490 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc @@ -1338,7 +1338,7 @@ TEST_P(ScrollingTest, NonFastScrollableRegionsForPlugins) { } #fixed { position: fixed; - top: 500px; + left: 300px; } </style> <div id="fixed"> diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc b/chromium/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc index 62c621a9e41..f79143f1a5c 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc @@ -21,6 +21,17 @@ namespace { // This is experimentally determined and corresponds to the UA decided // parameter as mentioned in spec. constexpr float kProximityRatio = 1.0 / 3.0; + +cc::SnapAlignment AdjustForRtlWritingMode(cc::SnapAlignment align) { + if (align == cc::SnapAlignment::kStart) + return cc::SnapAlignment::kEnd; + + if (align == cc::SnapAlignment::kEnd) + return cc::SnapAlignment::kStart; + + return align; +} + } // namespace // TODO(sunyunjia): Move the static functions to an anonymous namespace. @@ -328,25 +339,54 @@ void SnapCoordinator::UpdateSnapContainerData(LayoutBox& snap_container) { } } +// https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-align +// After normalization: +// * inline corresponds to x, and block corresponds to y +// * start corresponds to left or top +// * end corresponds to right or bottom +// In other words, the adjusted logical properties map to a physical layout +// as if the writing mode were horizontal left to right and top to bottom. static cc::ScrollSnapAlign GetPhysicalAlignment( const ComputedStyle& area_style, - const ComputedStyle& container_style) { + const ComputedStyle& container_style, + const PhysicalRect& area_rect, + const PhysicalRect& container_rect) { cc::ScrollSnapAlign align = area_style.GetScrollSnapAlign(); - if (container_style.IsHorizontalWritingMode()) - return align; - - cc::SnapAlignment tmp = align.alignment_inline; - align.alignment_inline = align.alignment_block; - align.alignment_block = tmp; - - if (container_style.IsFlippedBlocksWritingMode()) { - if (align.alignment_inline == cc::SnapAlignment::kStart) { - align.alignment_inline = cc::SnapAlignment::kEnd; - } else if (align.alignment_inline == cc::SnapAlignment::kEnd) { - align.alignment_inline = cc::SnapAlignment::kStart; - } + cc::ScrollSnapAlign adjusted_alignment; + // Start and end alignments are resolved with respect to the writing mode of + // the snap container unless the scroll snap area is larger than the snapport, + // in which case they are resolved with respect to the writing mode of the box + // itself. (This allows items in a container to have consistent snap alignment + // in general, while ensuring that start always aligns the item to allow + // reading its contents from the beginning.) + WritingDirectionMode writing_direction = + container_style.GetWritingDirection(); + WritingDirectionMode area_writing_direction = + area_style.GetWritingDirection(); + if (area_writing_direction.IsHorizontal()) { + if (area_rect.Width() > container_rect.Width()) + writing_direction = area_writing_direction; + } else { + if (area_rect.Height() > container_rect.Height()) + writing_direction = area_writing_direction; } - return align; + + bool rtl = (writing_direction.IsRtl()); + if (writing_direction.IsHorizontal()) { + adjusted_alignment.alignment_inline = + rtl ? AdjustForRtlWritingMode(align.alignment_inline) + : align.alignment_inline; + adjusted_alignment.alignment_block = align.alignment_block; + } else { + bool flipped = writing_direction.IsFlippedBlocks(); + adjusted_alignment.alignment_inline = + flipped ? AdjustForRtlWritingMode(align.alignment_block) + : align.alignment_block; + adjusted_alignment.alignment_block = + rtl ? AdjustForRtlWritingMode(align.alignment_inline) + : align.alignment_inline; + } + return adjusted_alignment; } cc::SnapAreaData SnapCoordinator::CalculateSnapAreaData( @@ -372,9 +412,10 @@ cc::SnapAreaData SnapCoordinator::CalculateSnapAreaData( area_rect.Expand(area_margin); snap_area_data.rect = FloatRect(area_rect); - cc::ScrollSnapAlign align = - GetPhysicalAlignment(*area_style, *container_style); - snap_area_data.scroll_snap_align = align; + PhysicalRect container_rect = snap_container.PhysicalBorderBoxRect(); + + snap_area_data.scroll_snap_align = GetPhysicalAlignment( + *area_style, *container_style, area_rect, container_rect); snap_area_data.must_snap = (area_style->ScrollSnapStop() == EScrollSnapStop::kAlways); diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc index bbf710d1038..e194eea0423 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc @@ -4,6 +4,8 @@ #include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h" +#include "components/shared_highlighting/core/common/shared_highlighting_features.h" +#include "components/shared_highlighting/core/common/text_fragments_utils.h" #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element.h" @@ -16,6 +18,7 @@ #include "third_party/blink/renderer/core/frame/local_frame.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/frame_load_request.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h" @@ -243,10 +246,6 @@ bool TextFragmentAnchor::Invoke() { frame_->GetDocument()->Markers().RemoveMarkersOfTypes( DocumentMarker::MarkerTypes::TextFragment()); - // TODO(bokan): Once BlockHTMLParserOnStyleSheets is launched, there won't be - // a way for the user to scroll before we invoke and scroll the anchor. We - // should confirm if we can remove tracking this after that point or if we - // need a replacement metric. if (user_scrolled_ && !did_scroll_into_view_) metrics_->ScrollCancelled(); @@ -290,7 +289,8 @@ void TextFragmentAnchor::DidScroll(mojom::blink::ScrollType type) { return; } - Dismiss(); + if (ShouldDismissOnScrollOrClick()) + Dismiss(); user_scrolled_ = true; if (did_non_zero_scroll_ && @@ -474,6 +474,16 @@ bool TextFragmentAnchor::Dismiss() { dismissed_ = true; metrics_->Dismissed(); + KURL url( + shared_highlighting::RemoveTextFragments(frame_->GetDocument()->Url())); + + // Replace the current history entry with the new url, so that the text + // fragment shown in the URL matches the state of the highlight on the page. + // This is equivalent to history.replaceState in javascript. + frame_->DomWindow()->document()->Loader()->RunURLAndHistoryUpdateSteps( + url, /*data=*/nullptr, WebFrameLoadType::kReplaceCurrentItem, + mojom::blink::ScrollRestorationType::kAuto); + return dismissed_; } @@ -514,4 +524,9 @@ bool TextFragmentAnchor::HasSearchEngineSource() { return IsKnownSearchEngine(referrer); } +bool TextFragmentAnchor::ShouldDismissOnScrollOrClick() { + return !base::FeatureList::IsEnabled( + shared_highlighting::kSharedHighlightingV2); +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h index 4ffc0281d75..755c7213430 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h @@ -86,6 +86,8 @@ class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor, void NoMatchFound() override {} + static bool ShouldDismissOnScrollOrClick(); + private: // Called when the search is finished. Reports metrics and activates the // element fragment anchor if we didn't find a match. diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h index b6f8d7c6900..8ec12602976 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h @@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_ANCHOR_METRICS_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_ANCHOR_METRICS_H_ +#include "base/time/tick_clock.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector.h" diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc index f1300e5bfb9..efb0a70eed0 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc @@ -839,12 +839,6 @@ INSTANTIATE_TEST_SUITE_P( // Test that the ScrollCancelled metric gets reported when a user scroll cancels // the scroll into view. TEST_P(TextFragmentAnchorScrollMetricsTest, ScrollCancelled) { - // This test isn't relevant with this flag enabled. When it's enabled, - // there's no way to block rendering and the fragment is installed and - // invoked as soon as parsing finishes which means the user cannot scroll - // before this point. - ScopedBlockHTMLParserOnStyleSheetsForTest block_parser(false); - SimRequest request("https://example.com/test.html#:~:text=test", "text/html"); SimSubresourceRequest css_request("https://example.com/test.css", "text/css"); LoadURL("https://example.com/test.html#:~:text=test"); diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc index a21b452b331..b3ddbadfcfb 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc @@ -2,11 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/test/scoped_feature_list.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/platform/scheduler/test/renderer_scheduler_test_support.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_mouse_event_init.h" #include "third_party/blink/renderer/core/dom/element.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/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" @@ -15,8 +20,11 @@ #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/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/page/context_menu_controller.h" #include "third_party/blink/renderer/core/page/scrolling/text_fragment_finder.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/scroll/scrollable_area.h" @@ -922,37 +930,12 @@ TEST_P(TextFragmentAnchorScrollTest, ScrollCancelled) { GetDocument().View()->UpdateAllLifecyclePhasesForTest(); mojom::blink::ScrollType scroll_type = GetParam(); - if (!RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled()) { - GetDocument().View()->LayoutViewport()->ScrollBy(ScrollOffset(0, 100), - scroll_type); - // Set the target text to visible and change its position to cause a layout - // and invoke the fragment anchor in the next begin frame. - css_request.Complete("p { visibility: visible; top: 1001px; }"); - img_request.Complete(""); - } else { - // Set the target text to visible and change its position to cause a layout - // and invoke the fragment anchor in the next begin frame. - css_request.Complete("p { visibility: visible; top: 1001px; }"); - RunPendingTasks(); - Compositor().BeginFrame(); - Element& p = *GetDocument().getElementById("text"); - - // We should have invoked the fragment and scrolled the <p> into view, but - // load should not yet be complete due to the image. - EXPECT_TRUE(ViewportRect().Contains(BoundingRectInFrame(p))); - ASSERT_FALSE(GetDocument().IsLoadCompleted()); - - // Before invoking again, perform a user scroll. This should abort future - // scrolls during fragment invocation. - GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 0), - scroll_type); - ASSERT_FALSE(ViewportRect().Contains(BoundingRectInFrame(p))); - - img_request.Complete(""); - RunPendingTasks(); - ASSERT_TRUE(GetDocument().IsLoadCompleted()); - } - + GetDocument().View()->LayoutViewport()->ScrollBy(ScrollOffset(0, 100), + scroll_type); + // Set the target text to visible and change its position to cause a layout + // and invoke the fragment anchor in the next begin frame. + css_request.Complete("p { visibility: visible; top: 1001px; }"); + img_request.Complete(""); RunAsyncMatchingTasks(); // Render two frames to handle the async step added by the beforematch event. @@ -983,6 +966,9 @@ TEST_P(TextFragmentAnchorScrollTest, ScrollCancelled) { // Test that user scrolling dismisses the highlight. TEST_P(TextFragmentAnchorScrollTest, DismissTextHighlightOnUserScroll) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndDisableFeature( + shared_highlighting::kSharedHighlightingV2); SimRequest request( "https://example.com/" "test.html#:~:text=test%20page&text=more%20text", @@ -1030,6 +1016,54 @@ TEST_P(TextFragmentAnchorScrollTest, DismissTextHighlightOnUserScroll) { } } +// Test that user scrolling doesn't dismiss the highlight, when the +// SharedHighlightingV2 flag is enabled. +TEST_P(TextFragmentAnchorScrollTest, DontDismissTextHighlightOnUserScroll) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndEnableFeature( + shared_highlighting::kSharedHighlightingV2); + SimRequest request( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text", + "text/html"); + LoadURL( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { + height: 2200px; + } + #first { + position: absolute; + top: 1000px; + } + #second { + position: absolute; + top: 2000px; + } + </style> + <p id="first">This is a test page</p> + <p id="second">With some more text</p> + )HTML"); + RunAsyncMatchingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + ASSERT_EQ(2u, GetDocument().Markers().Markers().size()); + + mojom::blink::ScrollType scroll_type = GetParam(); + LayoutViewport()->ScrollBy(ScrollOffset(0, -10), scroll_type); + + Compositor().BeginFrame(); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + EXPECT_TRUE(GetDocument().View()->GetFragmentAnchor()); +} + // Ensure that the text fragment anchor has no effect in an iframe. This is // disabled in iframes by design, for security reasons. TEST_F(TextFragmentAnchorTest, DisabledInIframes) { @@ -1482,6 +1516,9 @@ TEST_F(TextFragmentAnchorTest, CheckForWordBoundaryWithPartialWord) { // Test dismissing the text highlight with a click TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithClick) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndDisableFeature( + shared_highlighting::kSharedHighlightingV2); SimRequest request( "https://example.com/" "test.html#:~:text=test%20page&text=more%20text", @@ -1513,6 +1550,15 @@ TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithClick) { Compositor().BeginFrame(); Compositor().BeginFrame(); + KURL url = GetDocument() + .GetFrame() + ->Loader() + .GetDocumentLoader() + ->GetHistoryItem() + ->Url(); + EXPECT_EQ( + "https://example.com/test.html#:~:text=test%20page&text=more%20text", + url.GetString()); EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); SimulateClick(100, 100); @@ -1521,10 +1567,67 @@ TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithClick) { // Ensure the fragment is uninstalled EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); + url = GetDocument() + .GetFrame() + ->Loader() + .GetDocumentLoader() + ->GetHistoryItem() + ->Url(); + EXPECT_EQ("https://example.com/test.html", url.GetString()); +} + +// Test not dismissing the text highlight with a click, if the +// SharedHighlightingV2 flag is enabled. +TEST_F(TextFragmentAnchorTest, DontDismissTextHighlightWithClick) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndEnableFeature( + shared_highlighting::kSharedHighlightingV2); + SimRequest request( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text", + "text/html"); + LoadURL( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { + height: 2200px; + } + #first { + position: absolute; + top: 1000px; + } + #second { + position: absolute; + top: 2000px; + } + </style> + <p id="first">This is a test page</p> + <p id="second">With some more text</p> + )HTML"); + RunAsyncMatchingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + + SimulateClick(100, 100); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + + // Ensure the fragment is still installed + EXPECT_TRUE(GetDocument().View()->GetFragmentAnchor()); } // Test dismissing the text highlight with a tap TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithTap) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndDisableFeature( + shared_highlighting::kSharedHighlightingV2); SimRequest request( "https://example.com/" "test.html#:~:text=test%20page&text=more%20text", @@ -1556,6 +1659,15 @@ TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithTap) { Compositor().BeginFrame(); Compositor().BeginFrame(); + KURL url = GetDocument() + .GetFrame() + ->Loader() + .GetDocumentLoader() + ->GetHistoryItem() + ->Url(); + EXPECT_EQ( + "https://example.com/test.html#:~:text=test%20page&text=more%20text", + url.GetString()); EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); SimulateTap(100, 100); @@ -1564,10 +1676,67 @@ TEST_F(TextFragmentAnchorTest, DismissTextHighlightWithTap) { // Ensure the fragment is uninstalled EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); + url = GetDocument() + .GetFrame() + ->Loader() + .GetDocumentLoader() + ->GetHistoryItem() + ->Url(); + EXPECT_EQ("https://example.com/test.html", url.GetString()); +} + +// Test not dismissing the text highlight with a tap, if the +// SharedHighlightingV2 flag is enabled. +TEST_F(TextFragmentAnchorTest, DontDismissTextHighlightWithTap) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndEnableFeature( + shared_highlighting::kSharedHighlightingV2); + SimRequest request( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text", + "text/html"); + LoadURL( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { + height: 2200px; + } + #first { + position: absolute; + top: 1000px; + } + #second { + position: absolute; + top: 2000px; + } + </style> + <p id="first">This is a test page</p> + <p id="second">With some more text</p> + )HTML"); + RunAsyncMatchingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + + SimulateTap(100, 100); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + + // Ensure the fragment is installed + EXPECT_TRUE(GetDocument().View()->GetFragmentAnchor()); } // Test that we don't dismiss a text highlight before it's scrolled into view TEST_F(TextFragmentAnchorTest, DismissTextHighlightOutOfView) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndDisableFeature( + shared_highlighting::kSharedHighlightingV2); SimRequest request("https://example.com/test.html#:~:text=test", "text/html"); SimSubresourceRequest css_request("https://example.com/test.css", "text/css"); LoadURL("https://example.com/test.html#:~:text=test"); @@ -1609,6 +1778,9 @@ TEST_F(TextFragmentAnchorTest, DismissTextHighlightOutOfView) { // Test dismissing a text highlight that didn't require a scroll into view TEST_F(TextFragmentAnchorTest, DismissTextHighlightInView) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndDisableFeature( + shared_highlighting::kSharedHighlightingV2); SimRequest request( "https://example.com/" "test.html#:~:text=test%20page&text=more%20text", @@ -2127,6 +2299,86 @@ TEST_F(TextFragmentAnchorTest, IsInSameUninterruptedBlock_BlockInterruption) { EXPECT_FALSE(TextFragmentFinder::IsInSameUninterruptedBlock(start, end)); } +TEST_F(TextFragmentAnchorTest, OpenedFromHighlightDoesNotSelectAdditionalText) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndEnableFeature( + shared_highlighting::kSharedHighlightingV2); + SimRequest request("https://www.test.com/#:~:text=First%20test,page%20three", + "text/html"); + LoadURL("https://www.test.com/#:~:text=First%20test,page%20three"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + p { + font-size: 12px; + } + </style> + <p id="one">First test page one</p> + <p id="two">Second test page two</p> + <p id="three">Third test page three</p> + <p id="four">Fourth test page four</p> + </html>)HTML"); + RunAsyncMatchingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + Element* middle_element = GetDocument().getElementById("two"); + Element* last_element = GetDocument().getElementById("four"); + + WebView().GetSettings()->SetEditingBehavior( + mojom::EditingBehavior::kEditingMacBehavior); + + // Create a mouse event in the middle of <p> two. + WebMouseEvent mouse_down_event(WebInputEvent::Type::kMouseDown, + WebInputEvent::kNoModifiers, + WebInputEvent::GetStaticTimeStampForTests()); + const DOMRect* middle_rect = middle_element->getBoundingClientRect(); + gfx::PointF middle_elem_point(((middle_rect->left() + 1)), + ((middle_rect->top() + 1))); + mouse_down_event.SetPositionInWidget(middle_elem_point.x(), + middle_elem_point.y()); + mouse_down_event.SetPositionInScreen(middle_elem_point.x(), + middle_elem_point.y()); + mouse_down_event.click_count = 1; + mouse_down_event.button = WebMouseEvent::Button::kRight; + + // Corresponding release event (Windows shows context menu on release). + WebMouseEvent mouse_up_event(mouse_down_event); + mouse_up_event.SetType(WebInputEvent::Type::kMouseUp); + + WebView().MainFrameViewWidget()->HandleInputEvent( + WebCoalescedInputEvent(mouse_down_event, ui::LatencyInfo())); + WebView().MainFrameViewWidget()->HandleInputEvent( + WebCoalescedInputEvent(mouse_up_event, ui::LatencyInfo())); + + // No additional text should be selected. + FrameSelection& selection = GetDocument().GetFrame()->Selection(); + EXPECT_TRUE(selection.SelectedText().IsEmpty()); + + // Create a mouse event at the center of <p> four. + const DOMRect* last_rect = last_element->getBoundingClientRect(); + gfx::PointF last_elem_point(((last_rect->left() + 1)), + ((last_rect->top() + 1))); + mouse_down_event.SetPositionInWidget(last_elem_point.x(), + last_elem_point.y()); + mouse_down_event.SetPositionInScreen(last_elem_point.x(), + last_elem_point.y()); + + // Corresponding release event (Windows shows context menu on release). + WebMouseEvent last_mouse_up_event(mouse_down_event); + last_mouse_up_event.SetType(WebInputEvent::Type::kMouseUp); + + WebView().MainFrameViewWidget()->HandleInputEvent( + WebCoalescedInputEvent(mouse_down_event, ui::LatencyInfo())); + WebView().MainFrameViewWidget()->HandleInputEvent( + WebCoalescedInputEvent(last_mouse_up_event, ui::LatencyInfo())); + + // The text underneath the cursor should be selected. + EXPECT_FALSE(selection.SelectedText().IsEmpty()); +} + } // namespace } // namespace blink diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc new file mode 100644 index 00000000000..af6bea11989 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.cc @@ -0,0 +1,69 @@ +// Copyright 2021 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/page/scrolling/text_fragment_handler.h" + +#include "components/shared_highlighting/core/common/shared_highlighting_features.h" +#include "third_party/blink/public/common/browser_interface_broker_proxy.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/editing/position_with_affinity.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" + +namespace blink { + +TextFragmentHandler::TextFragmentHandler(LocalFrame* main_frame) + : text_fragment_selector_generator_( + MakeGarbageCollected<TextFragmentSelectorGenerator>(main_frame)) {} + +void TextFragmentHandler::BindTextFragmentReceiver( + mojo::PendingReceiver<mojom::blink::TextFragmentReceiver> producer) { + selector_producer_.reset(); + selector_producer_.Bind( + std::move(producer), + text_fragment_selector_generator_->GetFrame()->GetTaskRunner( + blink::TaskType::kInternalDefault)); +} + +TextFragmentSelectorGenerator* +TextFragmentHandler::GetTextFragmentSelectorGenerator() { + return text_fragment_selector_generator_; +} + +void TextFragmentHandler::Cancel() { + GetTextFragmentSelectorGenerator()->Cancel(); +} + +void TextFragmentHandler::RequestSelector(RequestSelectorCallback callback) { + GetTextFragmentSelectorGenerator()->RequestSelector(std::move(callback)); +} + +void TextFragmentHandler::RemoveFragments() { + DCHECK( + base::FeatureList::IsEnabled(shared_highlighting::kSharedHighlightingV2)); + + GetTextFragmentSelectorGenerator() + ->GetFrame() + ->View() + ->DismissFragmentAnchor(); +} + +// static +bool TextFragmentHandler::IsOverTextFragment(HitTestResult result) { + DocumentMarkerController& marker_controller = + result.InnerNodeFrame()->GetDocument()->Markers(); + PositionWithAffinity pos_with_affinity = result.GetPosition(); + const Position marker_position = pos_with_affinity.GetPosition(); + auto markers = marker_controller.MarkersAroundPosition( + ToPositionInFlatTree(marker_position), + DocumentMarker::MarkerTypes::TextFragment()); + return !markers.IsEmpty(); +} + +void TextFragmentHandler::Trace(Visitor* visitor) const { + visitor->Trace(text_fragment_selector_generator_); + visitor->Trace(selector_producer_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h new file mode 100644 index 00000000000..c3933da3136 --- /dev/null +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler.h @@ -0,0 +1,60 @@ +// Copyright 2021 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_HANDLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_HANDLER_H_ + +#include "third_party/blink/public/mojom/link_to_text/link_to_text.mojom-blink.h" +#include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h" +#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h" + +namespace blink { + +class LocalFrame; + +// TextFragmentHandler is responsible for handling text fragment operations +// on a LocalFrame. Generating text fragment selectors for a selection is +// delegated to TextFragmentSelectorGenerator. +class CORE_EXPORT TextFragmentHandler final + : public GarbageCollected<TextFragmentHandler>, + public blink::mojom::blink::TextFragmentReceiver { + public: + explicit TextFragmentHandler(LocalFrame* main_frame); + + void BindTextFragmentReceiver( + mojo::PendingReceiver<mojom::blink::TextFragmentReceiver> producer); + + // Cancel any pending selector requests. + void Cancel() override; + + // Requests selector for current selection. + void RequestSelector(RequestSelectorCallback callback) override; + + // Remove all text fragments from the current frame. + void RemoveFragments() override; + + // Determine if |result| represents a click on an existing highlight. + static bool IsOverTextFragment(HitTestResult result); + + void Trace(Visitor*) const; + + TextFragmentSelectorGenerator* GetTextFragmentSelectorGenerator(); + + private: + // Class responsible for generating text fragment selectors for the current + // selection. + Member<TextFragmentSelectorGenerator> text_fragment_selector_generator_; + + // Used for communication between |TextFragmentHandler| in renderer + // and |TextFragmentSelectorClientImpl| in browser. + HeapMojoReceiver<blink::mojom::blink::TextFragmentReceiver, + TextFragmentHandler> + selector_producer_{this, nullptr}; + + DISALLOW_COPY_AND_ASSIGN(TextFragmentHandler); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_TEXT_FRAGMENT_HANDLER_H_ diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc new file mode 100644 index 00000000000..e85446d2d5f --- /dev/null +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_handler_test.cc @@ -0,0 +1,87 @@ +// Copyright 2021 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/page/scrolling/text_fragment_handler.h" + +#include <gtest/gtest.h> + +#include "base/test/scoped_feature_list.h" +#include "components/shared_highlighting/core/common/shared_highlighting_features.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h" +#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.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/testing/unit_test_helpers.h" + +namespace blink { + +namespace { + +using test::RunPendingTasks; + +class TextFragmentHandlerTest : public SimTest { + public: + void SetUp() override { + SimTest::SetUp(); + WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600)); + } + + void RunAsyncMatchingTasks() { + auto* scheduler = + ThreadScheduler::Current()->GetWebMainThreadSchedulerForTest(); + blink::scheduler::RunIdleTasksForTesting(scheduler, + base::BindOnce([]() {})); + RunPendingTasks(); + } +}; + +TEST_F(TextFragmentHandlerTest, RemoveTextFragments) { + base::test::ScopedFeatureList feature_list_; + feature_list_.InitAndEnableFeature( + shared_highlighting::kSharedHighlightingV2); + SimRequest request( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text", + "text/html"); + LoadURL( + "https://example.com/" + "test.html#:~:text=test%20page&text=more%20text"); + request.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { + height: 2200px; + } + #first { + position: absolute; + top: 1000px; + } + #second { + position: absolute; + top: 2000px; + } + </style> + <p id="first">This is a test page</p> + <p id="second">With some more text</p> + )HTML"); + RunAsyncMatchingTasks(); + + // Render two frames to handle the async step added by the beforematch event. + Compositor().BeginFrame(); + Compositor().BeginFrame(); + + EXPECT_EQ(2u, GetDocument().Markers().Markers().size()); + + GetDocument().GetFrame()->GetTextFragmentHandler()->RemoveFragments(); + + EXPECT_EQ(0u, GetDocument().Markers().Markers().size()); + + // Ensure the fragment is uninstalled + EXPECT_FALSE(GetDocument().View()->GetFragmentAnchor()); +} + +} // namespace + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc index f61ac5e7f75..da327c933f3 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.cc @@ -5,6 +5,7 @@ #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h" #include "base/metrics/histogram_macros.h" +#include "base/strings/strcat.h" #include "base/time/default_tick_clock.h" #include "components/shared_highlighting/core/common/shared_highlighting_features.h" #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h" @@ -181,15 +182,15 @@ constexpr int kMaxRangeWords = 10; constexpr int kMaxIterationCountToRecord = 10; constexpr int kMinWordCount_ = 3; -void TextFragmentSelectorGenerator::UpdateSelection( - LocalFrame* selection_frame, - const EphemeralRangeInFlatTree& selection_range) { - DCHECK(selection_frame); - +TextFragmentSelectorGenerator::TextFragmentSelectorGenerator( + LocalFrame* main_frame) + : selection_frame_(main_frame) { // Scroll-to-text doesn't support iframes. - DCHECK(selection_frame->IsMainFrame()); + DCHECK(main_frame->IsMainFrame()); +} - selection_frame_ = selection_frame; +void TextFragmentSelectorGenerator::UpdateSelection( + const EphemeralRangeInFlatTree& selection_range) { selection_range_ = MakeGarbageCollected<Range>( selection_range.GetDocument(), ToPositionInDOMTree(selection_range.StartPosition()), @@ -201,17 +202,6 @@ void TextFragmentSelectorGenerator::UpdateSelection( } } -void TextFragmentSelectorGenerator::BindTextFragmentSelectorProducer( - mojo::PendingReceiver<mojom::blink::TextFragmentSelectorProducer> - producer) { - DCHECK(selection_frame_); - - selector_producer_.reset(); - selector_producer_.Bind( - std::move(producer), - selection_frame_->GetTaskRunner(blink::TaskType::kInternalDefault)); -} - void TextFragmentSelectorGenerator::AdjustSelection() { if (!selection_range_) return; @@ -436,14 +426,16 @@ void TextFragmentSelectorGenerator::ClearSelection() { if (selection_range_) { selection_range_->Dispose(); selection_range_ = nullptr; - selection_frame_ = nullptr; } } +void TextFragmentSelectorGenerator::Detach() { + selection_frame_ = nullptr; +} + void TextFragmentSelectorGenerator::Trace(Visitor* visitor) const { visitor->Trace(selection_frame_); visitor->Trace(selection_range_); - visitor->Trace(selector_producer_); visitor->Trace(finder_); } diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h index bd6be400528..25ef77500ce 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h @@ -15,6 +15,8 @@ namespace blink { +using RequestSelectorCallback = base::OnceCallback<void(const WTF::String&)>; + class LocalFrame; // TextFragmentSelectorGenerator is responsible for generating text fragment @@ -30,18 +32,12 @@ class LocalFrame; // match is uniquely identified or no new context/range can be added. class CORE_EXPORT TextFragmentSelectorGenerator final : public GarbageCollected<TextFragmentSelectorGenerator>, - public TextFragmentFinder::Client, - public blink::mojom::blink::TextFragmentSelectorProducer { + public TextFragmentFinder::Client { public: - explicit TextFragmentSelectorGenerator() = default; - - void BindTextFragmentSelectorProducer( - mojo::PendingReceiver<mojom::blink::TextFragmentSelectorProducer> - producer); + explicit TextFragmentSelectorGenerator(LocalFrame* main_frame); // Sets the frame and range of the current selection. - void UpdateSelection(LocalFrame* selection_frame, - const EphemeralRangeInFlatTree& selection_range); + void UpdateSelection(const EphemeralRangeInFlatTree& selection_range); // Adjust the selection start/end to a valid position. That includes skipping // non text start/end nodes and extending selection from start and end to @@ -49,10 +45,10 @@ class CORE_EXPORT TextFragmentSelectorGenerator final void AdjustSelection(); // blink::mojom::blink::TextFragmentSelectorProducer interface - void Cancel() override; + void Cancel(); // Requests selector for current selection. - void RequestSelector(RequestSelectorCallback callback) override; + void RequestSelector(RequestSelectorCallback callback); // TextFragmentFinder::Client interface void DidFindMatch(const EphemeralRangeInFlatTree& match, @@ -75,8 +71,12 @@ class CORE_EXPORT TextFragmentSelectorGenerator final // Releases members if necessary. void ClearSelection(); + void Detach(); + void Trace(Visitor*) const; + LocalFrame* GetFrame() { return selection_frame_; } + private: // Used for determining the next step of selector generation. enum GenerationStep { kExact, kRange, kContext }; @@ -136,11 +136,6 @@ class CORE_EXPORT TextFragmentSelectorGenerator final Member<Range> selection_range_; std::unique_ptr<TextFragmentSelector> selector_; - // Used for communication between |TextFragmentSelectorGenerator| in renderer - // and |TextFragmentSelectorClientImpl| in browser. - HeapMojoReceiver<blink::mojom::blink::TextFragmentSelectorProducer, - TextFragmentSelectorGenerator> - selector_producer_{this, nullptr}; RequestSelectorCallback pending_generate_selector_callback_; GenerationStep step_ = kExact; diff --git a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc index 473285eb21c..9060ddd81ce 100644 --- a/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc +++ b/chromium/third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator_test.cc @@ -98,9 +98,8 @@ class TextFragmentSelectorGeneratorTest GetDocument() .GetFrame() ->GetTextFragmentSelectorGenerator() - ->UpdateSelection(GetDocument().GetFrame(), - ToEphemeralRangeInFlatTree( - EphemeralRange(selected_start, selected_end))); + ->UpdateSelection(ToEphemeralRangeInFlatTree( + EphemeralRange(selected_start, selected_end))); bool callback_called = false; String selector; @@ -1270,7 +1269,6 @@ TEST_P(TextFragmentSelectorGeneratorTest, SecondGenerationCrash) { // This shouldn't crash. GetDocument().GetFrame()->GetTextFragmentSelectorGenerator()->UpdateSelection( - GetDocument().GetFrame(), ToEphemeralRangeInFlatTree(EphemeralRange(start, end))); base::RunLoop().RunUntilIdle(); } |