зеркало из https://github.com/mozilla/gecko-dev.git
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 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
7f699ce0f7
Коммит
924f5f1a55
|
@ -32,6 +32,9 @@ add_task(async function purgeHistoryTest() {
|
|||
content.history.pushState({}, "");
|
||||
content.history.pushState({}, "");
|
||||
content.history.back();
|
||||
await new Promise(function(r) {
|
||||
setTimeout(r);
|
||||
});
|
||||
let newHistory = content.history.length;
|
||||
Assert.equal(startHistory, 1, "Initial SHistory size");
|
||||
Assert.equal(newHistory, 3, "New SHistory size");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1034,12 +1034,25 @@ class nsDocShell final : public nsDocLoader,
|
|||
nsresult MaybeHandleLoadDelegate(nsDocShellLoadState* aLoadState,
|
||||
uint32_t aWindowType, bool* aDidHandleLoad);
|
||||
|
||||
// Check to see if we're loading a prior history entry in the same document.
|
||||
// If so, handle the scrolling or other action required instead of continuing
|
||||
// with new document navigation.
|
||||
struct SameDocumentNavigationState {
|
||||
nsAutoCString mCurrentHash;
|
||||
nsAutoCString mNewHash;
|
||||
bool mCurrentURIHasRef = false;
|
||||
bool mNewURIHasRef = false;
|
||||
bool mSameExceptHashes = false;
|
||||
bool mHistoryNavBetweenSameDoc = false;
|
||||
};
|
||||
|
||||
// Check to see if we're loading a prior history entry or doing a fragment
|
||||
// navigation in the same document.
|
||||
bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
||||
SameDocumentNavigationState& aState);
|
||||
|
||||
// ... If so, handle the scrolling or other action required instead of
|
||||
// continuing with new document navigation.
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult MaybeHandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
||||
bool* aWasSameDocument);
|
||||
nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
|
||||
SameDocumentNavigationState& aState);
|
||||
|
||||
private: // data members
|
||||
static nsIURIFixup* sURIFixup;
|
||||
|
|
|
@ -53,6 +53,21 @@ void ChildSHistory::Go(int32_t aOffset, ErrorResult& aRv) {
|
|||
aRv = mHistory->GotoIndex(index.value());
|
||||
}
|
||||
|
||||
void ChildSHistory::AsyncGo(int32_t aOffset) {
|
||||
if (!CanGo(aOffset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<PendingAsyncHistoryNavigation> asyncNav =
|
||||
new PendingAsyncHistoryNavigation(this, aOffset);
|
||||
mPendingNavigations.insertBack(asyncNav);
|
||||
NS_DispatchToCurrentThread(asyncNav.forget());
|
||||
}
|
||||
|
||||
void ChildSHistory::RemovePendingHistoryNavigations() {
|
||||
mPendingNavigations.clear();
|
||||
}
|
||||
|
||||
void ChildSHistory::EvictLocalContentViewers() {
|
||||
mHistory->EvictAllContentViewers();
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
|
||||
class nsSHistory;
|
||||
class nsDocShell;
|
||||
|
@ -62,6 +64,9 @@ class ChildSHistory : public nsISupports, public nsWrapperCache {
|
|||
*/
|
||||
bool CanGo(int32_t aOffset);
|
||||
void Go(int32_t aOffset, ErrorResult& aRv);
|
||||
void AsyncGo(int32_t aOffset);
|
||||
|
||||
void RemovePendingHistoryNavigations();
|
||||
|
||||
/**
|
||||
* Evicts all content viewers within the current process.
|
||||
|
@ -75,8 +80,31 @@ class ChildSHistory : public nsISupports, public nsWrapperCache {
|
|||
private:
|
||||
virtual ~ChildSHistory();
|
||||
|
||||
class PendingAsyncHistoryNavigation
|
||||
: public Runnable,
|
||||
public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> {
|
||||
public:
|
||||
PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset)
|
||||
: Runnable("PendingAsyncHistoryNavigation"),
|
||||
mHistory(aHistory),
|
||||
mOffset(aOffset) {}
|
||||
|
||||
NS_IMETHOD Run() override {
|
||||
if (isInList()) {
|
||||
remove();
|
||||
mHistory->Go(mOffset, IgnoreErrors());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<ChildSHistory> mHistory;
|
||||
int32_t mOffset;
|
||||
};
|
||||
|
||||
RefPtr<nsDocShell> mDocShell;
|
||||
RefPtr<nsSHistory> mHistory;
|
||||
mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -67,6 +67,11 @@ function end_test() {
|
|||
testWin.done();
|
||||
}
|
||||
|
||||
var gTestContinuation = null;
|
||||
function continueAsync() {
|
||||
setTimeout(function() { gTestContinuation.next(); })
|
||||
}
|
||||
|
||||
function test_basic_inner_navigation() {
|
||||
// Navigate the inner frame a few times
|
||||
loadContent(URL1, function() {
|
||||
|
@ -86,7 +91,8 @@ function test_basic_inner_navigation() {
|
|||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
test_state_navigation();
|
||||
gTestContinuation = test_state_navigation();
|
||||
gTestContinuation.next();
|
||||
});
|
||||
window.history.forward();
|
||||
});
|
||||
|
@ -95,7 +101,7 @@ function test_basic_inner_navigation() {
|
|||
});
|
||||
}
|
||||
|
||||
function test_state_navigation() {
|
||||
function* test_state_navigation() {
|
||||
window.location.hash = "STATE1";
|
||||
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
|
@ -107,19 +113,27 @@ function test_state_navigation() {
|
|||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState(), "STATE1", "State should be correct after going back");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState(), "STATE2", "State should be correct after going forward");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState(), "START", "State should be correct");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
|
@ -143,6 +157,8 @@ function test_state_navigation() {
|
|||
});
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(gState(), "START", "State should be correct");
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -124,6 +124,10 @@ function pageLoad() {
|
|||
}
|
||||
}
|
||||
|
||||
function continueAsync() {
|
||||
setTimeout(function() { gTestContinuation.next(); })
|
||||
}
|
||||
|
||||
function* testBody() {
|
||||
is(popup.scrollY, 0, "test 1");
|
||||
popup.scroll(0, 100);
|
||||
|
@ -133,16 +137,24 @@ function* testBody() {
|
|||
popup.scroll(0, 200); // set state-2's position to 200
|
||||
|
||||
popup.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(Math.round(popup.scrollY), 100, "test 3");
|
||||
popup.scroll(0, 150); // set original page's position to 150
|
||||
|
||||
popup.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(Math.round(popup.scrollY), 200, "test 4");
|
||||
|
||||
popup.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(Math.round(popup.scrollY), 150, "test 5");
|
||||
|
||||
popup.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(Math.round(popup.scrollY), 200, "test 6");
|
||||
|
||||
// At this point, the history looks like:
|
||||
|
@ -187,8 +199,12 @@ function* testBody() {
|
|||
is(Math.round(popup.scrollY), 200, "test 8");
|
||||
|
||||
popup.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(Math.round(popup.scrollY), 150, "test 9");
|
||||
popup.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(Math.round(popup.scrollY), 200, "test 10");
|
||||
|
||||
|
|
|
@ -17,23 +17,38 @@ SimpleTest.waitForExplicitFinish();
|
|||
|
||||
var popup = window.open("file_bug680257.html");
|
||||
|
||||
var gTestContinuation = null;
|
||||
function continueAsync() {
|
||||
setTimeout(function() { gTestContinuation.next(); })
|
||||
}
|
||||
|
||||
// The popup will call into popupLoaded() once it loads.
|
||||
function popupLoaded() {
|
||||
// runTests() needs to be called from outside popupLoaded's onload handler.
|
||||
// Otherwise, the navigations we do in runTests won't create new SHEntries.
|
||||
SimpleTest.executeSoon(runTests);
|
||||
SimpleTest.executeSoon(function() {
|
||||
if (!gTestContinuation) {
|
||||
gTestContinuation = runTests();
|
||||
}
|
||||
gTestContinuation.next();
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
function* runTests() {
|
||||
checkPopupLinkStyle(false, "Initial");
|
||||
|
||||
popup.location.hash = "a";
|
||||
checkPopupLinkStyle(true, "After setting hash");
|
||||
|
||||
popup.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
checkPopupLinkStyle(false, "After going back");
|
||||
|
||||
popup.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
checkPopupLinkStyle(true, "After going forward");
|
||||
|
||||
popup.close();
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
opener.is(shistory.index, 0, "check history index");
|
||||
opener.ok(webNav.canGoForward, "check canGoForward");
|
||||
window.history.forward();
|
||||
opener.is(shistory.legacySHistory.requestedIndex, 1, "check requestedIndex");
|
||||
},
|
||||
function() {
|
||||
opener.is(shistory.count, 2, "check history length");
|
||||
|
|
|
@ -70,6 +70,10 @@
|
|||
history.pushState({ state: "state2" }, "state2");
|
||||
window.scrollTo(0, 0);
|
||||
history.back();
|
||||
setTimeout(test);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state1's position");
|
||||
opener.is(history.state.state, "state1", "Unexpected state.");
|
||||
|
||||
|
@ -79,6 +83,10 @@
|
|||
history.pushState({ state: "state4" }, "state4");
|
||||
window.scrollTo(0, 0);
|
||||
history.back();
|
||||
setTimeout(test);
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
opener.is(Math.round(window.scrollY), 0, "Shouldn't have scrolled back to the state3's position");
|
||||
opener.is(history.state.state, "state3", "Unexpected state.");
|
||||
|
||||
|
@ -87,8 +95,16 @@
|
|||
document.getElementById("bottom").scrollIntoView();
|
||||
opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled to 'bottom'.");
|
||||
history.back();
|
||||
setTimeout(test);
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
window.scrollTo(0, 0);
|
||||
history.forward();
|
||||
setTimeout(test);
|
||||
break;
|
||||
}
|
||||
case 10: {
|
||||
opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state5's position");
|
||||
|
||||
var ifr = document.createElement("iframe");
|
||||
|
@ -97,12 +113,12 @@
|
|||
ifr.onload = test;
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
case 11: {
|
||||
oldHistoryObject = SpecialPowers.wrap(event.target).contentWindow.history;
|
||||
event.target.src = "about:blank";
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
case 12: {
|
||||
try {
|
||||
oldHistoryObject.scrollRestoration;
|
||||
opener.ok(false, "Should have thrown an exception.");
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsISHistory.h"
|
||||
#include "mozilla/dom/Location.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -159,7 +159,11 @@ void nsHistory::Go(int32_t aDelta, ErrorResult& aRv) {
|
|||
|
||||
// Ignore the return value from Go(), since returning errors from Go() can
|
||||
// lead to exceptions and a possible leak of history length
|
||||
session_history->Go(aDelta, IgnoreErrors());
|
||||
if (StaticPrefs::dom_window_history_async()) {
|
||||
session_history->AsyncGo(aDelta);
|
||||
} else {
|
||||
session_history->Go(aDelta, IgnoreErrors());
|
||||
}
|
||||
}
|
||||
|
||||
void nsHistory::Back(ErrorResult& aRv) {
|
||||
|
@ -177,7 +181,11 @@ void nsHistory::Back(ErrorResult& aRv) {
|
|||
return;
|
||||
}
|
||||
|
||||
sHistory->Go(-1, IgnoreErrors());
|
||||
if (StaticPrefs::dom_window_history_async()) {
|
||||
sHistory->AsyncGo(-1);
|
||||
} else {
|
||||
sHistory->Go(-1, IgnoreErrors());
|
||||
}
|
||||
}
|
||||
|
||||
void nsHistory::Forward(ErrorResult& aRv) {
|
||||
|
@ -195,7 +203,11 @@ void nsHistory::Forward(ErrorResult& aRv) {
|
|||
return;
|
||||
}
|
||||
|
||||
sHistory->Go(1, IgnoreErrors());
|
||||
if (StaticPrefs::dom_window_history_async()) {
|
||||
sHistory->AsyncGo(1);
|
||||
} else {
|
||||
sHistory->Go(1, IgnoreErrors());
|
||||
}
|
||||
}
|
||||
|
||||
void nsHistory::PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
|
||||
|
|
|
@ -83,6 +83,11 @@ function continue_test() {
|
|||
}
|
||||
}
|
||||
|
||||
var gTestContinuation = null;
|
||||
function continueAsync() {
|
||||
setTimeout(function() { gTestContinuation.next(); })
|
||||
}
|
||||
|
||||
function* test_basic_inner_navigation() {
|
||||
// Navigate the inner frame a few times
|
||||
yield loadContent(URL1);
|
||||
|
@ -118,25 +123,35 @@ function* test_state_navigation() {
|
|||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState, "STATE1", "State should be correct");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.forward();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState, "STATE2", "State should be correct");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
|
||||
is(gState, "START", "State should be correct");
|
||||
is(getURL(), URL2, "URL should be correct");
|
||||
is(getContent(), "Test2", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(gState, "START", "State should be correct");
|
||||
yield waitForLoad();
|
||||
|
||||
|
@ -144,6 +159,8 @@ function* test_state_navigation() {
|
|||
is(getContent(), "Test1", "Page should be correct");
|
||||
|
||||
window.history.back();
|
||||
continueAsync();
|
||||
yield;
|
||||
is(gState, "START", "State should be correct after going back twice");
|
||||
yield waitForLoad();
|
||||
|
||||
|
|
|
@ -2380,6 +2380,11 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
- name: dom.window.history.async
|
||||
type: bool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Enable the "noreferrer" feature argument for window.open()
|
||||
- name: dom.window.open.noreferrer.enabled
|
||||
type: bool
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
[scroll-restoration-fragment-scrolling-cross-origin.html]
|
||||
expected:
|
||||
if (os == "android") and e10s: ERROR
|
||||
[Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation]
|
||||
expected:
|
||||
if (os == "android") and e10s: TIMEOUT
|
||||
FAIL
|
||||
|
|
@ -5,9 +5,6 @@
|
|||
[pushState must not be allowed to create cross-origin URLs (data:URI)]
|
||||
expected: FAIL
|
||||
|
||||
[pushState must remove any tasks queued by the history traversal task source]
|
||||
expected: FAIL
|
||||
|
||||
[history.state should be a separate clone of the object, not a reference to the object passed to the event handler]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -5,12 +5,6 @@
|
|||
[replaceState must not be allowed to create cross-origin URLs (data:URI)]
|
||||
expected: FAIL
|
||||
|
||||
[replaceState must not remove any tasks queued by the history traversal task source]
|
||||
expected: FAIL
|
||||
|
||||
[.go must queue a task with the history traversal task source (run asynchronously)]
|
||||
expected: FAIL
|
||||
|
||||
[history.state should be a separate clone of the object, not a reference to the object passed to the event handler]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[004.html]
|
||||
[.go commands should be queued until the thread has ended]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[combination_history_004.html]
|
||||
expected: TIMEOUT
|
||||
[After calling of back method, check length]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[combination_history_005.html]
|
||||
expected: TIMEOUT
|
||||
[After calling of forward method, check length]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[combination_history_006.html]
|
||||
expected: TIMEOUT
|
||||
[After calling of go method, check length]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[combination_history_007.html]
|
||||
expected: TIMEOUT
|
||||
[After calling of back and pushState method, check length]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[history_back.html]
|
||||
expected: TIMEOUT
|
||||
[history back]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[history_forward.html]
|
||||
expected: TIMEOUT
|
||||
[history forward]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[history_go_minus.html]
|
||||
expected: TIMEOUT
|
||||
[history go minus]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[history_go_plus.html]
|
||||
expected: TIMEOUT
|
||||
[history go plus]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_1.html]
|
||||
[Multiple history traversals from the same task]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_2.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_3.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_4.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[traverse_the_history_5.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
[nested-context-navigations-iframe.html]
|
||||
[Test that iframe navigations are not observable by the parent, even after history navigations by the parent]
|
||||
expected: FAIL
|
||||
[Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent]
|
||||
expected: FAIL
|
|
@ -11,57 +11,63 @@
|
|||
}
|
||||
</style>
|
||||
<div id="log"></div>
|
||||
<iframe></iframe>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// The test does the following navigation steps for iframe
|
||||
// 1. load page-with-fragment.html#fragment
|
||||
// 2. load blank1
|
||||
// 3. go back to page-with-fragment.html
|
||||
async_test(function(t) {
|
||||
var iframe = document.querySelector('iframe');
|
||||
var hostInfo = get_host_info();
|
||||
var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
|
||||
var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment';
|
||||
var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html"
|
||||
var next;
|
||||
function frameOnload() {
|
||||
if (next) {
|
||||
next();
|
||||
dump("next \n");
|
||||
} else {
|
||||
dump("no next \n");
|
||||
// The test does the following navigation steps for iframe
|
||||
// 1. load page-with-fragment.html#fragment
|
||||
// 2. load blank1
|
||||
// 3. go back to page-with-fragment.html
|
||||
async_test(function(t) {
|
||||
var iframe = document.querySelector('iframe');
|
||||
var hostInfo = get_host_info();
|
||||
var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
|
||||
var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment';
|
||||
var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html"
|
||||
|
||||
var steps = [
|
||||
function() {
|
||||
iframe.src = 'resources/page-with-fragment.html#fragment';
|
||||
}, function() {
|
||||
assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page');
|
||||
// wait one animation frame to ensure layout is run and fragment scrolling is complete
|
||||
iframe.contentWindow.requestAnimationFrame(function() {
|
||||
assert_equals(iframe.contentWindow.scrollY, 800, 'should scroll to fragment');
|
||||
var steps = [
|
||||
function() {
|
||||
assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page');
|
||||
// wait one animation frame to ensure layout is run and fragment scrolling is complete
|
||||
iframe.contentWindow.requestAnimationFrame(function() {
|
||||
assert_approx_equals(iframe.contentWindow.scrollY, 800, 5, 'should scroll to fragment');
|
||||
|
||||
iframe.contentWindow.history.scrollRestoration = 'manual';
|
||||
assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual');
|
||||
setTimeout(next, 0);
|
||||
iframe.contentWindow.history.scrollRestoration = 'manual';
|
||||
assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual');
|
||||
setTimeout(next, 0);
|
||||
});
|
||||
}, function() {
|
||||
// navigate to a new page from a different origin
|
||||
iframe.src = remoteURL;
|
||||
}, function() {
|
||||
// going back causes the iframe to traverse back
|
||||
history.back();
|
||||
}, function() {
|
||||
// coming back from history, scrollRestoration should be set to manual and respected
|
||||
assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page');
|
||||
iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() {
|
||||
assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value');
|
||||
assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment');
|
||||
assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment');
|
||||
}));
|
||||
}
|
||||
];
|
||||
|
||||
var stepCount = 0;
|
||||
next = t.step_func(function() {
|
||||
steps[stepCount++]();
|
||||
});
|
||||
}, function() {
|
||||
// navigate to a new page from a different origin
|
||||
iframe.src = remoteURL;
|
||||
}, function() {
|
||||
// going back causes the iframe to traverse back
|
||||
history.back();
|
||||
}, function() {
|
||||
// coming back from history, scrollRestoration should be set to manual and respected
|
||||
assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page');
|
||||
iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() {
|
||||
assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value');
|
||||
assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment');
|
||||
assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment');
|
||||
}));
|
||||
}
|
||||
];
|
||||
|
||||
var stepCount = 0;
|
||||
var next = t.step_func(function() {
|
||||
steps[stepCount++]();
|
||||
});
|
||||
|
||||
iframe.onload = next;
|
||||
next();
|
||||
}, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation');
|
||||
next();
|
||||
}, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<iframe src="resources/page-with-fragment.html#fragment" onload="frameOnload()"></iframe>
|
||||
|
||||
|
|
|
@ -129,6 +129,11 @@ function ReferrerPolicyTestCase(scenario, testDescription, sanityChecker) {
|
|||
const expectedReferrer =
|
||||
referrerUrlResolver[scenario.referrer_url](currentURL);
|
||||
|
||||
function asyncResolve(result) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(result), 0);
|
||||
});}
|
||||
|
||||
// Request in the top-level document.
|
||||
promise_test(_ => {
|
||||
return invokeRequest(subresource, [])
|
||||
|
@ -146,7 +151,7 @@ function ReferrerPolicyTestCase(scenario, testDescription, sanityChecker) {
|
|||
subresource.url += "&-1";
|
||||
return invokeRequest(subresource, [])
|
||||
.then(result => checkResult(location.href, result))
|
||||
.finally(_ => history.back());
|
||||
.then(_ => history.back()).then(asyncResolve);
|
||||
}, "`Referer` header with length < 4k is not stripped to an origin.");
|
||||
|
||||
promise_test(_ => {
|
||||
|
@ -157,7 +162,7 @@ function ReferrerPolicyTestCase(scenario, testDescription, sanityChecker) {
|
|||
subresource.url += "&0";
|
||||
return invokeRequest(subresource, [])
|
||||
.then(result => checkResult(expectedReferrer, result))
|
||||
.finally(_ => history.back());
|
||||
.then(_ => history.back()).then(asyncResolve);
|
||||
}, "`Referer` header with length == 4k is not stripped to an origin.");
|
||||
|
||||
promise_test(_ => {
|
||||
|
@ -168,7 +173,7 @@ function ReferrerPolicyTestCase(scenario, testDescription, sanityChecker) {
|
|||
subresource.url += "&+1";
|
||||
return invokeRequest(subresource, [])
|
||||
.then(result => checkResult(originString, result))
|
||||
.finally(_ => history.back());
|
||||
.then(_ => history.back()).then(asyncResolve);
|
||||
}, "`Referer` header with length > 4k is stripped to an origin.");
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче