Commit 8e1e2bb6 authored by Nick Burris's avatar Nick Burris Committed by Commit Bot

Highlight text fragment directives on page reload

Fix the behavior where fragment anchors were only kept alive if a scroll
was needed, as is the behavior of element fragment anchors. This caused
text fragment anchors not to be kept alive and we didn't highlight on
reload.

Note that *sometimes* we would highlight on reload, due to a race
condition with fragment anchors, where we process the fragment before
restoring scroll position (which is where should_scroll becomes false),
see crbug/839292. This change makes it so we highlight on reload
independent of whether a scroll is needed.

Bug: 932551
Change-Id: I38cc82c4920255d8bcae9f8b55ac80f24d1465da
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1864714
Commit-Queue: David Bokan <bokan@chromium.org>
Reviewed-by: default avatarDavid Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706847}
parent 64fbff99
...@@ -1362,10 +1362,10 @@ void LocalFrameView::ProcessUrlFragment(const KURL& url, ...@@ -1362,10 +1362,10 @@ void LocalFrameView::ProcessUrlFragment(const KURL& url,
bool should_scroll) { bool should_scroll) {
// We want to create the anchor even if we don't need to scroll. This ensures // We want to create the anchor even if we don't need to scroll. This ensures
// all the side effects like setting CSS :target are correctly set. // all the side effects like setting CSS :target are correctly set.
FragmentAnchor* anchor = FragmentAnchor* anchor = FragmentAnchor::TryCreate(
FragmentAnchor::TryCreate(url, *frame_, same_document_navigation); url, *frame_, same_document_navigation, should_scroll);
if (anchor && should_scroll) { if (anchor) {
fragment_anchor_ = anchor; fragment_anchor_ = anchor;
fragment_anchor_->Installed(); fragment_anchor_->Installed();
......
...@@ -34,7 +34,8 @@ Node* FindAnchorFromFragment(const String& fragment, Document& doc) { ...@@ -34,7 +34,8 @@ Node* FindAnchorFromFragment(const String& fragment, Document& doc) {
} // namespace } // namespace
ElementFragmentAnchor* ElementFragmentAnchor::TryCreate(const KURL& url, ElementFragmentAnchor* ElementFragmentAnchor::TryCreate(const KURL& url,
LocalFrame& frame) { LocalFrame& frame,
bool should_scroll) {
DCHECK(frame.GetDocument()); DCHECK(frame.GetDocument());
Document& doc = *frame.GetDocument(); Document& doc = *frame.GetDocument();
...@@ -83,6 +84,10 @@ ElementFragmentAnchor* ElementFragmentAnchor::TryCreate(const KURL& url, ...@@ -83,6 +84,10 @@ ElementFragmentAnchor* ElementFragmentAnchor::TryCreate(const KURL& url,
if (!anchor_node) if (!anchor_node)
return nullptr; return nullptr;
// Element fragment anchors only need to be kept alive if they need scrolling.
if (!should_scroll)
return nullptr;
return MakeGarbageCollected<ElementFragmentAnchor>(*anchor_node, frame); return MakeGarbageCollected<ElementFragmentAnchor>(*anchor_node, frame);
} }
......
...@@ -29,7 +29,9 @@ class CORE_EXPORT ElementFragmentAnchor final : public FragmentAnchor { ...@@ -29,7 +29,9 @@ class CORE_EXPORT ElementFragmentAnchor final : public FragmentAnchor {
// Parses the URL fragment and, if possible, creates and returns a fragment // Parses the URL fragment and, if possible, creates and returns a fragment
// based on an Element in the page. Returns nullptr otherwise. Produces side // based on an Element in the page. Returns nullptr otherwise. Produces side
// effects related to fragment targeting in the page in either case. // effects related to fragment targeting in the page in either case.
static ElementFragmentAnchor* TryCreate(const KURL& url, LocalFrame& frame); static ElementFragmentAnchor* TryCreate(const KURL& url,
LocalFrame& frame,
bool should_scroll);
ElementFragmentAnchor(Node& anchor_node, LocalFrame& frame); ElementFragmentAnchor(Node& anchor_node, LocalFrame& frame);
~ElementFragmentAnchor() override = default; ~ElementFragmentAnchor() override = default;
......
...@@ -15,7 +15,8 @@ namespace blink { ...@@ -15,7 +15,8 @@ namespace blink {
FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url, FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url,
LocalFrame& frame, LocalFrame& frame,
bool same_document_navigation) { bool same_document_navigation,
bool should_scroll) {
DCHECK(frame.GetDocument()); DCHECK(frame.GetDocument());
FragmentAnchor* anchor = nullptr; FragmentAnchor* anchor = nullptr;
...@@ -28,7 +29,7 @@ FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url, ...@@ -28,7 +29,7 @@ FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url,
bool text_fragment_anchor_created = false; bool text_fragment_anchor_created = false;
if (text_fragment_identifiers_enabled) { if (text_fragment_identifiers_enabled) {
anchor = TextFragmentAnchor::TryCreateFragmentDirective( anchor = TextFragmentAnchor::TryCreateFragmentDirective(
url, frame, same_document_navigation); url, frame, same_document_navigation, should_scroll);
text_fragment_anchor_created = anchor; text_fragment_anchor_created = anchor;
} }
...@@ -60,7 +61,7 @@ FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url, ...@@ -60,7 +61,7 @@ FragmentAnchor* FragmentAnchor::TryCreate(const KURL& url,
bool element_id_anchor_found = false; bool element_id_anchor_found = false;
if (!anchor) { if (!anchor) {
anchor = ElementFragmentAnchor::TryCreate(url, frame); anchor = ElementFragmentAnchor::TryCreate(url, frame, should_scroll);
element_id_anchor_found = anchor; element_id_anchor_found = anchor;
} }
......
...@@ -32,7 +32,8 @@ class CORE_EXPORT FragmentAnchor : public GarbageCollected<FragmentAnchor> { ...@@ -32,7 +32,8 @@ class CORE_EXPORT FragmentAnchor : public GarbageCollected<FragmentAnchor> {
// will be performed, for example, setting/clearing :target and svgView(). // will be performed, for example, setting/clearing :target and svgView().
static FragmentAnchor* TryCreate(const KURL& url, static FragmentAnchor* TryCreate(const KURL& url,
LocalFrame& frame, LocalFrame& frame,
bool same_document_navigation); bool same_document_navigation,
bool should_scroll);
FragmentAnchor() = default; FragmentAnchor() = default;
virtual ~FragmentAnchor() = default; virtual ~FragmentAnchor() = default;
......
...@@ -77,7 +77,8 @@ bool CheckSecurityRestrictions(LocalFrame& frame, ...@@ -77,7 +77,8 @@ bool CheckSecurityRestrictions(LocalFrame& frame,
TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective( TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective(
const KURL& url, const KURL& url,
LocalFrame& frame, LocalFrame& frame,
bool same_document_navigation) { bool same_document_navigation,
bool should_scroll) {
DCHECK(RuntimeEnabledFeatures::TextFragmentIdentifiersEnabled( DCHECK(RuntimeEnabledFeatures::TextFragmentIdentifiersEnabled(
frame.GetDocument())); frame.GetDocument()));
...@@ -96,13 +97,16 @@ TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective( ...@@ -96,13 +97,16 @@ TextFragmentAnchor* TextFragmentAnchor::TryCreateFragmentDirective(
return nullptr; return nullptr;
} }
return MakeGarbageCollected<TextFragmentAnchor>(selectors, frame); return MakeGarbageCollected<TextFragmentAnchor>(selectors, frame,
should_scroll);
} }
TextFragmentAnchor::TextFragmentAnchor( TextFragmentAnchor::TextFragmentAnchor(
const Vector<TextFragmentSelector>& text_fragment_selectors, const Vector<TextFragmentSelector>& text_fragment_selectors,
LocalFrame& frame) LocalFrame& frame,
bool should_scroll)
: frame_(&frame), : frame_(&frame),
should_scroll_(should_scroll),
metrics_(MakeGarbageCollected<TextFragmentAnchorMetrics>( metrics_(MakeGarbageCollected<TextFragmentAnchorMetrics>(
frame_->GetDocument())) { frame_->GetDocument())) {
DCHECK(!text_fragment_selectors.IsEmpty()); DCHECK(!text_fragment_selectors.IsEmpty());
...@@ -134,7 +138,7 @@ bool TextFragmentAnchor::Invoke() { ...@@ -134,7 +138,7 @@ bool TextFragmentAnchor::Invoke() {
if (user_scrolled_ && !did_scroll_into_view_) if (user_scrolled_ && !did_scroll_into_view_)
metrics_->ScrollCancelled(); metrics_->ScrollCancelled();
first_match_needs_scroll_ = !user_scrolled_; first_match_needs_scroll_ = should_scroll_ && !user_scrolled_;
{ {
// FindMatch might cause scrolling and set user_scrolled_ so reset it when // FindMatch might cause scrolling and set user_scrolled_ so reset it when
...@@ -263,8 +267,8 @@ void TextFragmentAnchor::DidFinishSearch() { ...@@ -263,8 +267,8 @@ void TextFragmentAnchor::DidFinishSearch() {
dismissed_ = true; dismissed_ = true;
DCHECK(!element_fragment_anchor_); DCHECK(!element_fragment_anchor_);
element_fragment_anchor_ = element_fragment_anchor_ = ElementFragmentAnchor::TryCreate(
ElementFragmentAnchor::TryCreate(frame_->GetDocument()->Url(), *frame_); frame_->GetDocument()->Url(), *frame_, should_scroll_);
if (element_fragment_anchor_) { if (element_fragment_anchor_) {
// Schedule a frame so we can invoke the element anchor in // Schedule a frame so we can invoke the element anchor in
// PerformPreRafActions. // PerformPreRafActions.
...@@ -284,7 +288,7 @@ bool TextFragmentAnchor::Dismiss() { ...@@ -284,7 +288,7 @@ bool TextFragmentAnchor::Dismiss() {
if (!did_find_match_ || dismissed_) if (!did_find_match_ || dismissed_)
return true; return true;
DCHECK(did_scroll_into_view_ || user_scrolled_); DCHECK(!should_scroll_ || did_scroll_into_view_ || user_scrolled_);
frame_->GetDocument()->Markers().RemoveMarkersOfTypes( frame_->GetDocument()->Markers().RemoveMarkersOfTypes(
DocumentMarker::MarkerTypes::TextFragment()); DocumentMarker::MarkerTypes::TextFragment());
......
...@@ -36,11 +36,13 @@ class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor, ...@@ -36,11 +36,13 @@ class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor,
static TextFragmentAnchor* TryCreateFragmentDirective( static TextFragmentAnchor* TryCreateFragmentDirective(
const KURL& url, const KURL& url,
LocalFrame& frame, LocalFrame& frame,
bool same_document_navigation); bool same_document_navigation,
bool should_scroll);
TextFragmentAnchor( TextFragmentAnchor(
const Vector<TextFragmentSelector>& text_fragment_selectors, const Vector<TextFragmentSelector>& text_fragment_selectors,
LocalFrame& frame); LocalFrame& frame,
bool should_scroll);
~TextFragmentAnchor() override = default; ~TextFragmentAnchor() override = default;
bool Invoke() override; bool Invoke() override;
...@@ -89,6 +91,10 @@ class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor, ...@@ -89,6 +91,10 @@ class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor,
// Whether the text fragment anchor has been dismissed yet. This should be // Whether the text fragment anchor has been dismissed yet. This should be
// kept alive until dismissed so we can remove text highlighting. // kept alive until dismissed so we can remove text highlighting.
bool dismissed_ = false; bool dismissed_ = false;
// Whether we should scroll the anchor into view. This will be false for
// history navigations and reloads, where we want to restore the highlight but
// not scroll into view again.
bool should_scroll_ = false;
Member<TextFragmentAnchorMetrics> metrics_; Member<TextFragmentAnchorMetrics> metrics_;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/core/frame/local_frame.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/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/location.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/html/html_element.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/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h" #include "third_party/blink/renderer/core/input/event_handler.h"
...@@ -1545,6 +1546,46 @@ TEST_F(TextFragmentAnchorTest, TextDirectiveInSvg) { ...@@ -1545,6 +1546,46 @@ TEST_F(TextFragmentAnchorTest, TextDirectiveInSvg) {
EXPECT_EQ(1u, GetDocument().Markers().Markers().size()); EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
} }
// Ensure we restore the text highlight on page reload
TEST_F(TextFragmentAnchorTest, HighlightOnReload) {
SimRequest request("https://example.com/test.html#:~:text=test", "text/html");
LoadURL("https://example.com/test.html#:~:text=test");
const String& html = R"HTML(
<!DOCTYPE html>
<style>
body {
height: 1200px;
}
p {
position: absolute;
top: 1000px;
}
</style>
<p id="text">This is a test page</p>
)HTML";
request.Complete(html);
Compositor().BeginFrame();
RunAsyncMatchingTasks();
EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
// Tap to dismiss the highlight.
SimulateClick(10, 10);
EXPECT_EQ(0u, GetDocument().Markers().Markers().size());
// Reload the page and expect the highlight to be restored.
SimRequest reload_request("https://example.com/test.html#:~:text=test",
"text/html");
MainFrame().StartReload(WebFrameLoadType::kReload);
reload_request.Complete(html);
Compositor().BeginFrame();
RunAsyncMatchingTasks();
EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
}
} // namespace } // namespace
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment