Bug 582361 - Align scrolling to a fragment with HTML spec. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D187362
This commit is contained in:
Mathew Hodson 2023-09-13 16:04:59 +00:00
parent f5107ef981
commit 9b4650bb45
7 changed files with 100 additions and 146 deletions

View File

@ -10850,6 +10850,7 @@ nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
return NS_OK;
}
// https://html.spec.whatwg.org/#scrolling-to-a-fragment
nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
nsACString& aNewHash, uint32_t aLoadType) {
if (!mCurrentURI) {
@ -10879,88 +10880,71 @@ 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 (!aNewHash.IsEmpty()) {
// anchor is there, but if it's a load from history,
// we don't have any anchor jumping to do
// 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;
// We assume that the bytes are in UTF-8, as it says in the
// spec:
// http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
// We try the UTF-8 string first, and then try the document's
// charset (see below). If the string is not UTF-8,
// conversion will fail and give us an empty Unicode string.
// In that case, we should just fall through to using the
// page's charset.
nsresult rv = NS_ERROR_FAILURE;
NS_ConvertUTF8toUTF16 uStr(aNewHash);
if (!uStr.IsEmpty()) {
rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
}
if (NS_FAILED(rv)) {
char* str = ToNewCString(aNewHash, mozilla::fallible);
if (!str) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsUnescape(str);
NS_ConvertUTF8toUTF16 utf16Str(str);
if (!utf16Str.IsEmpty()) {
rv = presShell->GoToAnchor(utf16Str, scroll,
ScrollFlags::ScrollSmoothAuto);
}
free(str);
}
// Above will fail if the anchor name is not UTF-8. Need to
// convert from document charset to unicode.
if (NS_FAILED(rv)) {
// Get a document charset
NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
Document* doc = mContentViewer->GetDocument();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsAutoCString charset;
doc->GetDocumentCharacterSet()->Name(charset);
nsCOMPtr<nsITextToSubURI> textToSubURI =
do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Unescape and convert to unicode
nsAutoString uStr;
rv = textToSubURI->UnEscapeAndConvert(charset, aNewHash, uStr);
NS_ENSURE_SUCCESS(rv, rv);
// Ignore return value of GoToAnchor, since it will return an error
// if there is no such anchor in the document, which is actually a
// success condition for us (we want to update the session history
// with the new URI no matter whether we actually scrolled
// somewhere).
if (aNewHash.IsEmpty()) {
// 2. If fragment is the empty string, then return the special value top of
// the document.
//
// When aNewHash contains "%00", unescaped string may be empty.
// And GoToAnchor asserts if we ask it to scroll to an empty ref.
presShell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(),
ScrollFlags::ScrollSmoothAuto);
}
} else {
// Tell the shell it's at an anchor, without scrolling.
// Tell the shell it's at an anchor without scrolling.
presShell->GoToAnchor(u""_ns, false);
// An empty anchor was found, but if it's a load from history,
// we don't have to jump to the top of the page. Scrollbar
// position will be restored by the caller, based on positions
// stored in session history.
if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) {
return NS_OK;
}
// An empty anchor. Scroll to the top of the page. Ignore the
// return value; failure to scroll here (e.g. if there is no
// root scrollframe) is not grounds for canceling the load!
if (scroll) {
// Scroll to the top of the page. Ignore the return value; failure to
// scroll here (e.g. if there is no root scrollframe) is not grounds for
// canceling the load!
SetCurScrollPosEx(0, 0);
}
return NS_OK;
}
// 3. Let potentialIndicatedElement be the result of finding a potential
// indicated element given document and fragment.
NS_ConvertUTF8toUTF16 uStr(aNewHash);
auto rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
// 4. If potentialIndicatedElement is not null, then return
// potentialIndicatedElement.
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
// 5. Let fragmentBytes be the result of percent-decoding fragment.
nsAutoCString fragmentBytes;
const bool unescaped = NS_UnescapeURL(aNewHash.Data(), aNewHash.Length(),
/* aFlags = */ 0, fragmentBytes);
if (!unescaped) {
// Another attempt is only necessary if characters were unescaped.
return NS_OK;
}
if (fragmentBytes.IsEmpty()) {
// When aNewHash contains "%00", the unescaped string may be empty, and
// GoToAnchor asserts if we ask it to scroll to an empty ref.
presShell->GoToAnchor(u""_ns, false);
return NS_OK;
}
// 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
// fragmentBytes.
nsAutoString decodedFragment;
rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
NS_ENSURE_SUCCESS(rv, rv);
// 7. Set potentialIndicatedElement to the result of finding a potential
// indicated element given document and decodedFragment.
//
// Ignore the return value of GoToAnchor, since it will return an error if
// there is no such anchor in the document, which is actually a success
// condition for us (we want to update the session history with the new URI no
// matter whether we actually scrolled somewhere).
presShell->GoToAnchor(decodedFragment, scroll, ScrollFlags::ScrollSmoothAuto);
return NS_OK;
}

View File

@ -13108,6 +13108,7 @@ void Document::SetScrollToRef(nsIURI* aDocumentURI) {
}
}
// https://html.spec.whatwg.org/#scrolling-to-a-fragment
void Document::ScrollToRef() {
if (mScrolledToRefAlready) {
RefPtr<PresShell> presShell = GetPresShell();
@ -13117,53 +13118,52 @@ void Document::ScrollToRef() {
return;
}
// 2. If fragment is the empty string, then return the special value top of
// the document.
if (mScrollToRef.IsEmpty()) {
return;
}
RefPtr<PresShell> presShell = GetPresShell();
if (presShell) {
nsresult rv = NS_ERROR_FAILURE;
// We assume that the bytes are in UTF-8, as it says in the spec:
// http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
if (!presShell) {
return;
}
// 3. Let potentialIndicatedElement be the result of finding a potential
// indicated element given document and fragment.
NS_ConvertUTF8toUTF16 ref(mScrollToRef);
// Check an empty string which might be caused by the UTF-8 conversion
if (!ref.IsEmpty()) {
// Note that GoToAnchor will handle flushing layout as needed.
rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
} else {
rv = NS_ERROR_FAILURE;
}
auto rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
if (NS_FAILED(rv)) {
nsAutoCString buff;
const bool unescaped =
NS_UnescapeURL(mScrollToRef.BeginReading(), mScrollToRef.Length(),
/*aFlags =*/0, buff);
// This attempt is only necessary if characters were unescaped.
if (unescaped) {
NS_ConvertUTF8toUTF16 utf16Str(buff);
if (!utf16Str.IsEmpty()) {
rv = presShell->GoToAnchor(utf16Str,
mChangeScrollPosWhenScrollingToRef);
}
}
// If UTF-8 URI failed then try to assume the string as a
// document's charset.
if (NS_FAILED(rv)) {
const Encoding* encoding = GetDocumentCharacterSet();
rv = encoding->DecodeWithoutBOMHandling(unescaped ? buff : mScrollToRef,
ref);
if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
rv = presShell->GoToAnchor(ref, mChangeScrollPosWhenScrollingToRef);
}
}
}
// 4. If potentialIndicatedElement is not null, then return
// potentialIndicatedElement.
if (NS_SUCCEEDED(rv)) {
mScrolledToRefAlready = true;
return;
}
// 5. Let fragmentBytes be the result of percent-decoding fragment.
nsAutoCString fragmentBytes;
const bool unescaped =
NS_UnescapeURL(mScrollToRef.Data(), mScrollToRef.Length(),
/* aFlags = */ 0, fragmentBytes);
if (!unescaped || fragmentBytes.IsEmpty()) {
// Another attempt is only necessary if characters were unescaped.
return;
}
// 6. Let decodedFragment be the result of running UTF-8 decode without BOM on
// fragmentBytes.
nsAutoString decodedFragment;
rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(fragmentBytes, decodedFragment);
NS_ENSURE_SUCCESS_VOID(rv);
// 7. Set potentialIndicatedElement to the result of finding a potential
// indicated element given document and decodedFragment.
rv = presShell->GoToAnchor(decodedFragment,
mChangeScrollPosWhenScrollingToRef);
if (NS_SUCCEEDED(rv)) {
mScrolledToRefAlready = true;
}
}

View File

@ -416,20 +416,11 @@ void nsHtml5TreeOperation::SetHTMLElementAttributes(
nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
nsString value; // Not Auto, because using it to hold nsStringBuffer*
val.ToString(value);
if (nsGkAtoms::a == aName && nsGkAtoms::name == localName) {
// This is an HTML5-incompliant Geckoism.
// Remove when fixing bug 582361
NS_ConvertUTF16toUTF8 cname(value);
NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
aElement->SetAttr(nsuri, localName, prefix, uv, false);
} else {
aElement->SetAttr(nsuri, localName, prefix, value, false);
}
}
}
}
nsIContent* nsHtml5TreeOperation::CreateHTMLElement(

View File

@ -1,4 +0,0 @@
[fragment-and-encoding-2.html]
max-asserts: 4
expected:
if (os == "android") and fission: [OK, TIMEOUT]

View File

@ -1,6 +0,0 @@
[fragment-and-encoding.html]
max-asserts: 5
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[%FF should not find U+00FF as decoding it gives U+FFFD]
expected: FAIL

View File

@ -1,5 +0,0 @@
[scroll-frag-non-utf8-encoded-document.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Fragment Navigation: fragment id should not be found in non UTF8 document]
expected: FAIL

View File

@ -1,6 +0,0 @@
[scroll-frag-percent-encoded.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[Fragment Navigation: fragment id should be percent-decoded]
expected:
if os == "android": FAIL