Bug 1155730, implement History.scrollRestoration r=jst

This commit is contained in:
Olli Pettay 2015-12-26 12:59:09 +02:00
Родитель 8b2a176dce
Коммит 2b2b2cc690
16 изменённых файлов: 300 добавлений и 20 удалений

Просмотреть файл

@ -154,10 +154,14 @@ var SessionHistoryInternal = {
if (shEntry.contentType)
entry.contentType = shEntry.contentType;
let x = {}, y = {};
shEntry.getScrollPosition(x, y);
if (x.value != 0 || y.value != 0)
entry.scroll = x.value + "," + y.value;
if (shEntry.scrollRestorationIsManual) {
entry.scrollRestorationIsManual = true;
} else {
let x = {}, y = {};
shEntry.getScrollPosition(x, y);
if (x.value != 0 || y.value != 0)
entry.scroll = x.value + "," + y.value;
}
// Collect owner data for the current history entry.
try {
@ -338,7 +342,9 @@ var SessionHistoryInternal = {
entry.structuredCloneVersion);
}
if (entry.scroll) {
if (entry.scrollRestorationIsManual) {
shEntry.scrollRestorationIsManual = true;
} else if (entry.scroll) {
var scrollPos = (entry.scroll || "0,0").split(",");
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);

Просмотреть файл

@ -10043,9 +10043,11 @@ nsDocShell::InternalLoad(nsIURI* aURI,
nsCOMPtr<nsIInputStream> postData;
nsCOMPtr<nsISupports> cacheKey;
bool scrollRestorationIsManual = false;
if (mOSHE) {
/* save current position of scroller(s) (bug 59774) */
mOSHE->SetScrollPosition(cx, cy);
mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
// Get the postdata and page ident from the current page, if
// the new load is being done via normal means. Note that
// "normal means" can be checked for just by checking for
@ -10060,6 +10062,11 @@ nsDocShell::InternalLoad(nsIURI* aURI,
// cache data, since the two SHEntries correspond to the
// same document.
if (mLSHE) {
if (!aSHEntry) {
// If we're not doing a history load, scroll restoration
// should be inherited from the previous session history entry.
mLSHE->SetScrollRestorationIsManual(scrollRestorationIsManual);
}
mLSHE->AdoptBFCacheEntry(mOSHE);
}
}
@ -10132,10 +10139,12 @@ nsDocShell::InternalLoad(nsIURI* aURI,
/* restore previous position of scroller(s), if we're moving
* back in history (bug 59774)
*/
nscoord bx, by;
nscoord bx = 0;
nscoord by = 0;
bool needsScrollPosUpdate = false;
if (mOSHE && (aLoadType == LOAD_HISTORY ||
aLoadType == LOAD_RELOAD_NORMAL)) {
aLoadType == LOAD_RELOAD_NORMAL) &&
!scrollRestorationIsManual) {
needsScrollPosUpdate = true;
mOSHE->GetScrollPosition(&bx, &by);
}
@ -11624,6 +11633,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
GetCurScrollPos(ScrollOrientation_Y, &cy);
mOSHE->SetScrollPosition(cx, cy);
bool scrollRestorationIsManual = false;
mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
// Since we're not changing which page we have loaded, pass
// true for aCloneChildren.
rv = AddToSessionHistory(newURI, nullptr, nullptr, true,
@ -11632,6 +11644,10 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
// Session history entries created by pushState inherit scroll restoration
// mode from the current entry.
newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);
// Link the new SHEntry to the old SHEntry's BFCache entry, since the
// two entries correspond to the same document.
NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), NS_ERROR_FAILURE);
@ -11740,6 +11756,27 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual)
{
*aIsManual = false;
if (mOSHE) {
mOSHE->GetScrollRestorationIsManual(aIsManual);
}
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual)
{
if (mOSHE) {
mOSHE->SetScrollRestorationIsManual(aIsManual);
}
return NS_OK;
}
bool
nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI)
{
@ -12140,10 +12177,21 @@ nsDocShell::PersistLayoutHistoryState()
nsresult rv = NS_OK;
if (mOSHE) {
bool scrollRestorationIsManual = false;
mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual);
nsCOMPtr<nsIPresShell> shell = GetPresShell();
nsCOMPtr<nsILayoutHistoryState> layoutState;
if (shell) {
nsCOMPtr<nsILayoutHistoryState> layoutState;
rv = shell->CaptureHistoryState(getter_AddRefs(layoutState));
} else if (scrollRestorationIsManual) {
// Even if we don't have layout anymore, we may want to reset the current
// scroll state in layout history.
GetLayoutHistoryState(getter_AddRefs(layoutState));
}
if (scrollRestorationIsManual && layoutState) {
layoutState->ResetScrollState();
}
}

Просмотреть файл

@ -43,7 +43,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(bc3524bd-023c-4fc8-ace1-472bc999fb12)]
[scriptable, builtinclass, uuid(811aa3e1-7c4d-45ae-89da-ea1b107c60ed)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@ -1090,4 +1090,10 @@ interface nsIDocShell : nsIDocShellTreeItem
* top level chrome docshell.
*/
attribute boolean windowDraggingAllowed;
/**
* Sets/gets the current scroll restoration mode.
* @see https://html.spec.whatwg.org/#dom-history-scroll-restoration
*/
attribute boolean currentScrollRestorationIsManual;
};

Просмотреть файл

@ -30,7 +30,7 @@ class nsSHEntryShared;
[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
[ptr] native nsSHEntryShared(nsSHEntryShared);
[scriptable, uuid(3a5e5fa0-5364-4fbb-a87a-3f12a6b51903)]
[scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
interface nsISHEntry : nsISupports
{
/**
@ -301,6 +301,12 @@ interface nsISHEntry : nsISupports
* for example with view-source.
*/
attribute nsIURI baseURI;
/**
* Sets/gets the current scroll restoration state,
* if true == "manual", false == "auto".
*/
attribute boolean scrollRestorationIsManual;
};
[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]

Просмотреть файл

@ -33,6 +33,7 @@ nsSHEntry::nsSHEntry()
, mParent(nullptr)
, mURIWasModified(false)
, mIsSrcdocEntry(false)
, mScrollRestorationIsManual(false)
{
}
@ -53,6 +54,7 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
, mURIWasModified(aOther.mURIWasModified)
, mStateData(aOther.mStateData)
, mIsSrcdocEntry(aOther.mIsSrcdocEntry)
, mScrollRestorationIsManual(false)
, mSrcdocData(aOther.mSrcdocData)
, mBaseURI(aOther.mBaseURI)
{
@ -598,6 +600,20 @@ nsSHEntry::SetBaseURI(nsIURI* aBaseURI)
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual)
{
*aIsManual = mScrollRestorationIsManual;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetScrollRestorationIsManual(bool aIsManual)
{
mScrollRestorationIsManual = aIsManual;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetChildCount(int32_t* aCount)
{

Просмотреть файл

@ -64,6 +64,7 @@ private:
bool mURIWasModified;
nsCOMPtr<nsIStructuredCloneContainer> mStateData;
bool mIsSrcdocEntry;
bool mScrollRestorationIsManual;
nsString mSrcdocData;
nsCOMPtr<nsIURI> mBaseURI;
};

Просмотреть файл

@ -0,0 +1,128 @@
<html>
<head>
<script>
var oldHistoryObject = null;
function test(event) {
if (!opener.scrollRestorationTest) {
opener.scrollRestorationTest = 0;
}
++opener.scrollRestorationTest;
switch (opener.scrollRestorationTest) {
case 1: {
opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
opener.ok(history.scrollRestoration, "History object has scrollRestoration property.");
opener.ok(history.scrollRestoration, "auto", "history.scrollRestoration's default value should be 'auto'.");
history.scrollRestoration = "foobar";
opener.ok(history.scrollRestoration, "auto", "Invalid enum value should not change the value of an attribute.");
history.scrollRestoration = "manual";
opener.ok(history.scrollRestoration, "manual", "Valid enum value should change the value of an attribute.");
history.scrollRestoration = "auto";
opener.ok(history.scrollRestoration, "auto", "Valid enum value should change the value of an attribute.");
document.getElementById("bottom").scrollIntoView();
window.location.reload(false);
break;
}
case 2: {
opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
opener.isnot(window.scrollY, 0, "Should have restored scrolling.");
opener.is(history.scrollRestoration, "auto", "Should have the same scrollRestoration as before reload.");
history.scrollRestoration = "manual";
window.onunload = function() {} // Disable bfcache.
window.location.reload(false);
break;
}
case 3: {
opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
opener.is(window.scrollY, 0, "Should not have restored scrolling.");
opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload.");
document.getElementById("bottom").scrollIntoView();
window.onunload = null; // Should get bfcache behavior.
opener.setTimeout("testWindow.history.back();", 250);
window.location.href = 'data:text/html,';
break;
}
case 4: {
opener.is(event.persisted, true, "Should have persisted session history entry.");
opener.isnot(window.scrollY, 0, "Should have kept the old scroll position.");
opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload.");
window.scrollTo(0, 0);
window.location.hash = "hash";
requestAnimationFrame(test);
break;
}
case 5: {
opener.isnot(window.scrollY, 0, "Should have scrolled to #hash.");
opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation.");
window.onunload = function() {} // Disable bfcache.
opener.setTimeout("is(testWindow.history.scrollRestoration, 'auto'); testWindow.history.back();", 250);
window.location.href = 'data:text/html,';
break;
}
case 6: {
opener.is(event.persisted, false, "Shouldn't have persisted session history entry.");
opener.is(window.scrollY, 0, "Shouldn't have kept the old scroll position.");
opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation.");
history.scrollRestoration = "auto";
document.getElementById("bottom").scrollIntoView();
history.pushState({ state: "state1" }, "state1");
history.pushState({ state: "state2" }, "state2");
window.scrollTo(0, 0);
history.back();
opener.isnot(window.scrollY, 0, "Should have scrolled back to the state1's position");
opener.is(history.state.state, "state1", "Unexpected state.");
history.scrollRestoration = "manual";
document.getElementById("bottom").scrollIntoView();
history.pushState({ state: "state3" }, "state3");
history.pushState({ state: "state4" }, "state4");
window.scrollTo(0, 0);
history.back();
opener.is(window.scrollY, 0, "Shouldn't have scrolled back to the state3's position");
opener.is(history.state.state, "state3", "Unexpected state.");
var ifr = document.createElement("iframe");
ifr.src = "data:text/html,";
document.body.appendChild(ifr);
ifr.onload = test;
break;
}
case 7: {
oldHistoryObject = event.target.contentWindow.history;
event.target.src = "about:blank";
break;
}
case 8: {
try {
var sr = oldHistoryObject.scrollRestoration;
opener.ok(false, "Should have thrown an exception.");
} catch(ex) {
opener.isnot(ex, null, "Did get an exception");
}
try {
oldHistoryObject.scrollRestoration = "auto";
opener.ok(false, "Should have thrown an exception.");
} catch(ex) {
opener.isnot(ex, null, "Did get an exception");
}
opener.nextTest();
window.close();
break;
}
}
}
window.addEventListener("pageshow",
function(e) {
setTimeout(test, 0, e);
});
</script>
</head>
<body>
<div style="border: 1px solid black; height: 5000px;">
&nbsp;</div>
<div id="bottom">Hello world</div>
<a href="#hash" name="hash">hash</a>
</body>
</html>

Просмотреть файл

@ -10,6 +10,7 @@ support-files =
file_document_write_1.html
file_fragment_handling_during_load.html
file_nested_frames.html
file_scrollRestoration.html
file_shiftReload_and_pushState.html
file_static_and_dynamic_1.html
frame0.html

Просмотреть файл

@ -29,7 +29,8 @@ var testFiles =
"file_bug534178.html", // Session history transaction clean-up.
"file_fragment_handling_during_load.html",
"file_nested_frames.html",
"file_shiftReload_and_pushState.html"
"file_shiftReload_and_pushState.html",
"file_scrollRestoration.html"
];
var testCount = 0; // Used by the test files.

Просмотреть файл

@ -7,7 +7,6 @@
#include "nsHistory.h"
#include "jsapi.h"
#include "mozilla/dom/HistoryBinding.h"
#include "nsCOMPtr.h"
#include "nsPIDOMWindow.h"
#include "nsIDocument.h"
@ -96,6 +95,38 @@ nsHistory::GetLength(ErrorResult& aRv) const
return len >= 0 ? len : 0;
}
ScrollRestoration
nsHistory::GetScrollRestoration(mozilla::ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return mozilla::dom::ScrollRestoration::Auto;
}
bool currentScrollRestorationIsManual = false;
win->GetDocShell()->
GetCurrentScrollRestorationIsManual(&currentScrollRestorationIsManual);
return currentScrollRestorationIsManual ?
mozilla::dom::ScrollRestoration::Manual :
mozilla::dom::ScrollRestoration::Auto;
}
void
nsHistory::SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
mozilla::ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
if (!win || !win->HasActiveDocument() || !win->GetDocShell()) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
win->GetDocShell()->
SetCurrentScrollRestorationIsManual(
aMode == mozilla::dom::ScrollRestoration::Manual);
}
void
nsHistory::GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
ErrorResult& aRv) const

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/HistoryBinding.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMHistory.h"
@ -36,6 +37,9 @@ public:
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
uint32_t GetLength(mozilla::ErrorResult& aRv) const;
mozilla::dom::ScrollRestoration GetScrollRestoration(mozilla::ErrorResult& aRv);
void SetScrollRestoration(mozilla::dom::ScrollRestoration aMode,
mozilla::ErrorResult& aRv);
void GetState(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
mozilla::ErrorResult& aRv) const;
void Go(int32_t aDelta, mozilla::ErrorResult& aRv);

Просмотреть файл

@ -11,10 +11,14 @@
* and create derivative works of this document.
*/
enum ScrollRestoration { "auto", "manual" };
interface History {
[Throws]
readonly attribute unsigned long length;
[Throws]
attribute ScrollRestoration scrollRestoration;
[Throws]
readonly attribute any state;
[Throws, UnsafeInPrerendering]
void go(optional long delta = 0);

Просмотреть файл

@ -2084,6 +2084,11 @@ nsDocumentViewer::Hide(void)
nsCOMPtr<nsIDocShell> docShell(mContainer);
if (docShell) {
#ifdef DEBUG
nsCOMPtr<nsIContentViewer> currentViewer;
docShell->GetContentViewer(getter_AddRefs(currentViewer));
MOZ_ASSERT(currentViewer == this);
#endif
nsCOMPtr<nsILayoutHistoryState> layoutState;
mPresShell->CaptureHistoryState(getter_AddRefs(layoutState));
}

Просмотреть файл

@ -18,8 +18,8 @@ class nsPresState;
template<typename> struct already_AddRefed;
#define NS_ILAYOUTHISTORYSTATE_IID \
{ 0x5208993e, 0xd812, 0x431e, \
{ 0x95, 0x9c, 0xc3, 0x84, 0x5b, 0x6e, 0x5a, 0xce } }
{ 0xaef27cb3, 0x4df9, 0x4eeb, \
{ 0xb0, 0xb0, 0xac, 0x56, 0xcf, 0x86, 0x1d, 0x04 } }
class nsILayoutHistoryState : public nsISupports {
public:
@ -53,6 +53,11 @@ class nsILayoutHistoryState : public nsISupports {
* or all possible history
*/
virtual void SetScrollPositionOnly(const bool aFlag) = 0;
/**
* Resets nsPresState::GetScrollState of all nsPresState objects to 0,0.
*/
virtual void ResetScrollState() = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsILayoutHistoryState,

Просмотреть файл

@ -36,7 +36,8 @@ public:
HasStates() const override;
virtual void
SetScrollPositionOnly(const bool aFlag) override;
virtual void
ResetScrollState() override;
private:
~nsLayoutHistoryState() {}
@ -94,3 +95,14 @@ nsLayoutHistoryState::SetScrollPositionOnly(const bool aFlag)
{
mScrollPositionOnly = aFlag;
}
void
nsLayoutHistoryState::ResetScrollState()
{
for (auto iter = mStates.Iter(); !iter.Done(); iter.Next()) {
nsPresState* state = iter.UserData();
if (state) {
state->SetScrollState(nsPoint(0, 0));
}
}
}

Просмотреть файл

@ -756,10 +756,14 @@ SessionStore.prototype = {
entry.contentType = aEntry.contentType;
}
let x = {}, y = {};
aEntry.getScrollPosition(x, y);
if (x.value != 0 || y.value != 0) {
entry.scroll = x.value + "," + y.value;
if (aEntry.scrollRestorationIsManual) {
entry.scrollRestorationIsManual = true;
} else {
let x = {}, y = {};
aEntry.getScrollPosition(x, y);
if (x.value != 0 || y.value != 0) {
entry.scroll = x.value + "," + y.value;
}
}
if (aEntry.owner) {
@ -862,7 +866,9 @@ SessionStore.prototype = {
shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);
}
if (aEntry.scroll) {
if (aEntry.scrollRestorationIsManual) {
shEntry.scrollRestorationIsManual = true;
} else if (aEntry.scroll) {
let scrollPos = aEntry.scroll.split(",");
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);