Bug 1563587, Make history.back/forward/go asynchronous, r=farre

The main part of the change is the change to ChildSHistory - make it possible to have Go() to be called asynchronously
and also let one to cancel pending history navigations. History object (window.history) can then use either the sync or
async Go(), depending on the dom.window.history.async pref.

LoadDelegate, which is used by GeckoView, needs special handling, since
it spins event loop nestedly. With session history loads and same-document loads we can just
bypass it.
To deal with same-document case, MaybeHandleSameDocumentNavigation is split to IsSameDocumentNavigation,
which collects relevant information about the request and returns true if same-document navigation should happen,
and then later HandleSameDocumentNavigation uses that information to trigger the navigation.
SameDocumentNavigationState is used to pass the information around.

referrer-policy-test-case.sub.js is buggy causing tests to pass only on Firefox with sync history API.

nested-context-navigations-iframe.html.ini is added because of https://bugzilla.mozilla.org/show_bug.cgi?id=1572932

Differential Revision: https://phabricator.services.mozilla.com/D41199
This commit is contained in:
Olli Pettay
2019-08-14 06:38:47 +00:00
parent e5c013e044
commit cc4f459e90
34 changed files with 325 additions and 210 deletions

View File

@@ -21,6 +21,7 @@
#include "mozilla/BasePrincipal.h"
#include "mozilla/Casting.h"
#include "mozilla/Components.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Encoding.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
@@ -8933,70 +8934,65 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState,
return rv;
}
nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
nsDocShellLoadState* aLoadState, bool* aWasSameDocument) {
bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
SameDocumentNavigationState& aState) {
MOZ_ASSERT(aLoadState);
MOZ_ASSERT(aWasSameDocument);
*aWasSameDocument = false;
if (!(aLoadState->LoadType() == LOAD_NORMAL ||
aLoadState->LoadType() == LOAD_STOP_CONTENT ||
LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
LOAD_FLAGS_REPLACE_HISTORY) ||
aLoadState->LoadType() == LOAD_HISTORY ||
aLoadState->LoadType() == LOAD_LINK)) {
return NS_OK;
return false;
}
nsresult rv;
nsCOMPtr<nsIURI> currentURI = mCurrentURI;
nsAutoCString curHash, newHash;
bool curURIHasRef = false, newURIHasRef = false;
nsresult rvURINew = aLoadState->URI()->GetRef(newHash);
nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash);
if (NS_SUCCEEDED(rvURINew)) {
rvURINew = aLoadState->URI()->GetHasRef(&newURIHasRef);
rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
}
bool sameExceptHashes = false;
if (currentURI && NS_SUCCEEDED(rvURINew)) {
nsresult rvURIOld = currentURI->GetRef(curHash);
nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
if (NS_SUCCEEDED(rvURIOld)) {
rvURIOld = currentURI->GetHasRef(&curURIHasRef);
rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef);
}
if (NS_SUCCEEDED(rvURIOld)) {
if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(),
&sameExceptHashes))) {
sameExceptHashes = false;
&aState.mSameExceptHashes))) {
aState.mSameExceptHashes = false;
}
}
}
if (!sameExceptHashes && sURIFixup && currentURI && NS_SUCCEEDED(rvURINew)) {
if (!aState.mSameExceptHashes && sURIFixup && currentURI &&
NS_SUCCEEDED(rvURINew)) {
// Maybe aLoadState->URI() came from the exposable form of currentURI?
nsCOMPtr<nsIURI> currentExposableURI;
rv = sURIFixup->CreateExposableURI(currentURI,
getter_AddRefs(currentExposableURI));
NS_ENSURE_SUCCESS(rv, rv);
nsresult rvURIOld = currentExposableURI->GetRef(curHash);
DebugOnly<nsresult> rv = sURIFixup->CreateExposableURI(
currentURI, getter_AddRefs(currentExposableURI));
MOZ_ASSERT(NS_SUCCEEDED(rv), "CreateExposableURI should not fail, ever!");
nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash);
if (NS_SUCCEEDED(rvURIOld)) {
rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef);
rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef);
}
if (NS_SUCCEEDED(rvURIOld)) {
if (NS_FAILED(currentExposableURI->EqualsExceptRef(aLoadState->URI(),
&sameExceptHashes))) {
sameExceptHashes = false;
if (NS_FAILED(currentExposableURI->EqualsExceptRef(
aLoadState->URI(), &aState.mSameExceptHashes))) {
aState.mSameExceptHashes = false;
}
}
}
bool historyNavBetweenSameDoc = false;
if (mOSHE && aLoadState->SHEntry()) {
// We're doing a history load.
mOSHE->SharesDocumentWith(aLoadState->SHEntry(), &historyNavBetweenSameDoc);
mOSHE->SharesDocumentWith(aLoadState->SHEntry(),
&aState.mHistoryNavBetweenSameDoc);
#ifdef DEBUG
if (historyNavBetweenSameDoc) {
if (aState.mHistoryNavBetweenSameDoc) {
nsCOMPtr<nsIInputStream> currentPostData = mOSHE->GetPostData();
NS_ASSERTION(currentPostData == aLoadState->PostDataStream(),
"Different POST data for entries for the same page?");
@@ -9019,13 +9015,21 @@ nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
// that history.go(0) and the like trigger full refreshes, rather than
// same document navigations.
bool doSameDocumentNavigation =
(historyNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
(aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
(!aLoadState->SHEntry() && !aLoadState->PostDataStream() &&
sameExceptHashes && newURIHasRef);
aState.mSameExceptHashes && aState.mNewURIHasRef);
if (!doSameDocumentNavigation) {
return NS_OK;
}
return doSameDocumentNavigation;
}
nsresult nsDocShell::HandleSameDocumentNavigation(
nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) {
#ifdef DEBUG
SameDocumentNavigationState state;
MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state));
#endif
nsCOMPtr<nsIURI> currentURI = mCurrentURI;
// Save the position of the scrollers.
nsPoint scrollPos = GetCurScrollPos();
@@ -9184,8 +9188,8 @@ nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
// arguments it receives. But even if we don't end up scrolling,
// ScrollToAnchor performs other important tasks, such as informing
// the presShell that we have a new hash. See bug 680257.
rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash,
aLoadState->LoadType());
nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef,
aState.mNewHash, aLoadState->LoadType());
NS_ENSURE_SUCCESS(rv, rv);
/* restore previous position of scroller(s), if we're moving
@@ -9209,10 +9213,11 @@ nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
// reference to avoid null derefs. See bug 914521.
if (win) {
// Fire a hashchange event URIs differ, and only in their hashes.
bool doHashchange = sameExceptHashes && (curURIHasRef != newURIHasRef ||
!curHash.Equals(newHash));
bool doHashchange = aState.mSameExceptHashes &&
(aState.mCurrentURIHasRef != aState.mNewURIHasRef ||
!aState.mCurrentHash.Equals(aState.mNewHash));
if (historyNavBetweenSameDoc || doHashchange) {
if (aState.mHistoryNavBetweenSameDoc || doHashchange) {
win->DispatchSyncPopState();
}
@@ -9227,7 +9232,6 @@ nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
}
}
*aWasSameDocument = true;
return NS_OK;
}
@@ -9275,14 +9279,22 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
// If we don't have a target, we're loading into ourselves, and our load
// delegate may want to intercept that load.
bool handled;
rv = MaybeHandleLoadDelegate(
aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled);
if (NS_FAILED(rv)) {
return rv;
}
if (handled) {
return NS_OK;
SameDocumentNavigationState sameDocumentNavigationState;
bool sameDocument =
IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState);
// LoadDelegate has already had chance to delegate loads which ended up to
// session history, so no need to re-delegate here, and we don't want fragment
// navigations to go through load delegate.
if (!sameDocument && !(aLoadState->LoadType() & LOAD_CMD_HISTORY)) {
bool handled;
rv = MaybeHandleLoadDelegate(
aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled);
if (NS_FAILED(rv)) {
return rv;
}
if (handled) {
return NS_OK;
}
}
// If a source docshell has been passed, check to see if we are sandboxed
@@ -9391,10 +9403,9 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
// See if this is actually a load between two history entries for the same
// document. If the process fails, or if we successfully navigate within the
// same document, return.
bool wasSameDocument;
rv = MaybeHandleSameDocumentNavigation(aLoadState, &wasSameDocument);
if (NS_FAILED(rv) || wasSameDocument) {
return rv;
if (sameDocument) {
return HandleSameDocumentNavigation(aLoadState,
sameDocumentNavigationState);
}
// mContentViewer->PermitUnload can destroy |this| docShell, which
@@ -9601,6 +9612,18 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
// If we have a saved content viewer in history, restore and show it now.
if (aLoadState->SHEntry() && (mLoadType & LOAD_CMD_HISTORY)) {
// https://html.spec.whatwg.org/#history-traversal:
// To traverse the history
// "If entry has a different Document object than the current entry, then
// run the following substeps: Remove any tasks queued by the history
// traversal task source..."
// Same document object case was handled already above with
// HandleSameDocumentNavigation call.
RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
if (shistory) {
shistory->RemovePendingHistoryNavigations();
}
// It's possible that the previous viewer of mContentViewer is the
// viewer that will end up in aLoadState->SHEntry() when it gets closed. If
// that's the case, we need to go ahead and force it into its shentry so we
@@ -11261,6 +11284,14 @@ nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI,
nsCOMPtr<nsISHEntry> newSHEntry;
if (!aReplace) {
// Step 2.
// Step 2.2, "Remove any tasks queued by the history traversal task
// source..."
RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
if (shistory) {
shistory->RemovePendingHistoryNavigations();
}
// Save the current scroll position (bug 590573). Step 2.3.
nsPoint scrollPos = GetCurScrollPos();
mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);