Bug 1895555 - Text Fragments: Implement same-document navigation. r=farre,dom-core

Same-document navigation follows a different code path than normal navigation
and was therefore not covered in the initial implementation for text fragments.
Same-document navigation does not set a URI in the `Document`, which
is the way cross-document navigation would parse text directives from the URL.

Instead, `nsDocShell::ScrollToAnchor()` is called via
`nsDocShell::InternalLoad()`-> `nsDocShell::HandleSameDocumentNavigation()`.
This code path needs to parse and remove the fragment directive from the new
fragment to be able to find text fragments and to allow for element-id fallback.
`nsDocShell::ScrollToAnchor()` needs to start an attempt to scroll to the text fragment
if it exists. It must not, however, clear the uninvoked text directives,  because a
same-document navigation could happen before the document is fully loaded,
hence the target text might not be part of the DOM tree.

As per spec, a second attempt to scroll to the text fragment is done after the load
is completed. This is done by `Document::ScrollToRef()`, which is called by
`nsDocumentViewer::LoadComplete()` after the load has finished.
This call will clear the uninvoked directives.

Differential Revision: https://phabricator.services.mozilla.com/D209726
This commit is contained in:
Jan-Niklas Jaeschke
2024-05-17 12:16:00 +00:00
parent d554a723e9
commit b78b4a3fe3
9 changed files with 91 additions and 44 deletions

View File

@@ -64,6 +64,7 @@
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FragmentDirective.h"
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/dom/HTMLIFrameElement.h"
#include "mozilla/dom/PerformanceNavigation.h"
@@ -8476,6 +8477,17 @@ bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
}
// A Fragment Directive must be removed from the new hash in order to allow
// fallback element id scroll. Additionally, the extracted parsed text
// directives need to be stored for further use.
nsTArray<TextDirective> textDirectives;
if (FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragmentString(
aState.mNewHash, &textDirectives)) {
if (Document* doc = GetDocument()) {
doc->FragmentDirective()->SetTextDirectives(std::move(textDirectives));
}
}
if (currentURI && NS_SUCCEEDED(rvURINew)) {
nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
if (NS_SUCCEEDED(rvURIOld)) {
@@ -10701,6 +10713,24 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
rootScroll->ClearDidHistoryRestore();
}
// If it's a load from history, we don't have any anchor jumping to do.
// Scrollbar position will be restored by the caller based on positions stored
// in session history.
bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL;
// If the load contains text directives, try to apply them. This may fail if
// the load is a same-document load that was initiated before the document was
// fully loaded and the target is not yet included in the DOM tree.
// For this case, the `uninvokedTextDirectives` are not cleared, so that
// `Document::ScrollToRef()` can re-apply the text directive.
// `Document::ScrollToRef()` is (presumably) the second "async" call mentioned
// in sec. 7.4.2.3.3 in the HTML spec, "Fragment navigations":
// https://html.spec.whatwg.org/#scroll-to-fragid:~:text=This%20algorithm%20will%20be%20called%20twice
const bool hasScrolledToTextFragment =
presShell->HighlightAndGoToTextFragment(scroll);
if (hasScrolledToTextFragment) {
return NS_OK;
}
// If we have no new anchor, we do not want to scroll, unless there is a
// current anchor and we are doing a history load. So return if we have no
// new anchor, and there is no current anchor or the load is not a history
@@ -10712,11 +10742,6 @@ nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
// Both the new and current URIs refer to the same page. We can now
// browse to the hash stored in the new URI.
// If it's a load from history, we don't have any anchor jumping to do.
// Scrollbar position will be restored by the caller based on positions stored
// in session history.
bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL;
if (aNewHash.IsEmpty()) {
// 2. If fragment is the empty string, then return the special value top of
// the document.