Bug 1683751 - Removing iframes should update history.length r=peterv

Differential Revision: https://phabricator.services.mozilla.com/D100274
This commit is contained in:
Olli Pettay 2021-02-03 00:02:29 +00:00
Родитель b245b926b3
Коммит affb60b285
21 изменённых файлов: 345 добавлений и 28 удалений

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

@ -2077,6 +2077,10 @@ PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel(
return abuse;
}
void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1);
}
std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
@ -3170,11 +3174,11 @@ void BrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
}
}
void BrowsingContext::RemoveFromSessionHistory() {
void BrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
if (XRE_IsContentProcess()) {
ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this);
ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this, aChangeID);
} else {
Canonical()->RemoveFromSessionHistory();
Canonical()->RemoveFromSessionHistory(aChangeID);
}
}

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

@ -190,7 +190,10 @@ enum class ExplicitActiveStatus : uint8_t {
FIELD(AuthorStyleDisabledDefault, bool) \
FIELD(DisplayMode, mozilla::dom::DisplayMode) \
/* True if the top level browsing context owns a main media controller */ \
FIELD(HasMainMediaController, bool)
FIELD(HasMainMediaController, bool) \
/* The number of entries added to the session history because of this \
* browsing context. */ \
FIELD(HistoryEntryCount, uint32_t)
// BrowsingContext, in this context, is the cross process replicated
// environment in which information about documents is stored. In
@ -749,7 +752,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void RemoveDynEntriesFromActiveSessionHistoryEntry();
// Removes entries corresponding to this BrowsingContext from session history.
void RemoveFromSessionHistory();
void RemoveFromSessionHistory(const nsID& aChangeID);
void SetTriggeringAndInheritPrincipals(nsIPrincipal* aTriggeringPrincipal,
nsIPrincipal* aPrincipalToInherit,
@ -784,6 +787,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
PopupBlocker::PopupControlState RevisePopupAbuseLevel(
PopupBlocker::PopupControlState aControl);
void IncrementHistoryEntryCountForBrowsingContext();
protected:
virtual ~BrowsingContext();
BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,

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

@ -377,6 +377,7 @@ CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
if (!entry) {
return nullptr;
}
Unused << SetHistoryEntryCount(entry->BCHistoryLength());
} else {
entry = new SessionHistoryEntry(aLoadState, aChannel);
if (IsTop()) {
@ -730,9 +731,10 @@ void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
}
void CanonicalBrowsingContext::RemoveFromSessionHistory() {
void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
if (shistory) {
CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
bool didRemove;
AutoTArray<nsID, 16> ids({GetHistoryID()});
@ -749,6 +751,7 @@ void CanonicalBrowsingContext::RemoveFromSessionHistory() {
}
}
}
HistoryCommitIndexAndLength(aChangeID, caller);
}
}

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

@ -150,7 +150,7 @@ class CanonicalBrowsingContext final : public BrowsingContext {
void RemoveDynEntriesFromActiveSessionHistoryEntry();
void RemoveFromSessionHistory();
void RemoveFromSessionHistory(const nsID& aChangeID);
void HistoryGo(int32_t aIndex, uint64_t aHistoryEpoch,
bool aRequireUserInteraction,

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

@ -9070,6 +9070,7 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
} else {
mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
// FIXME We should probably just compute mChildOffset in the parent
// instead of passing it over IPC here.
mBrowsingContext->SetActiveSessionHistoryEntry(
@ -11824,6 +11825,7 @@ void nsDocShell::UpdateActiveEntry(
if (replace) {
mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
} else {
mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
// FIXME We should probably just compute mChildOffset in the parent
// instead of passing it over IPC here.
mBrowsingContext->SetActiveSessionHistoryEntry(
@ -13461,6 +13463,9 @@ void nsDocShell::MoveLoadingToActiveEntry(bool aPersist) {
this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
mLoadingEntry.swap(loadingEntry);
if (!mActiveEntryIsLoadingFromSessionHistory) {
mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
}
}
if (mActiveEntry) {

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

@ -397,7 +397,8 @@ SessionHistoryEntry::SessionHistoryEntry(SessionHistoryInfo* aInfo)
SessionHistoryEntry::SessionHistoryEntry(const SessionHistoryEntry& aEntry)
: mInfo(MakeUnique<SessionHistoryInfo>(*aEntry.mInfo)),
mParent(aEntry.mParent),
mID(aEntry.mID) {}
mID(aEntry.mID),
mBCHistoryLength(aEntry.mBCHistoryLength) {}
SessionHistoryEntry::~SessionHistoryEntry() {
// Null out the mParent pointers on all our kids.

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

@ -227,6 +227,92 @@ struct LoadingSessionHistoryInfo {
bool mLoadingCurrentActiveEntry = false;
};
// HistoryEntryCounterForBrowsingContext is used to count the number of entries
// which are added to the session history for a particular browsing context.
// If a SessionHistoryEntry is cloned because of navigation in some other
// browsing context, that doesn't cause the counter value to be increased.
// The browsing context specific counter is needed to make it easier to
// synchronously update history.length value in a child process when
// an iframe is removed from DOM.
class HistoryEntryCounterForBrowsingContext {
public:
HistoryEntryCounterForBrowsingContext()
: mCounter(new RefCountedCounter()), mHasModified(false) {
++(*this);
}
HistoryEntryCounterForBrowsingContext(
const HistoryEntryCounterForBrowsingContext& aOther)
: mCounter(aOther.mCounter), mHasModified(false) {}
HistoryEntryCounterForBrowsingContext(
HistoryEntryCounterForBrowsingContext&& aOther) = delete;
~HistoryEntryCounterForBrowsingContext() {
if (mHasModified) {
--(*mCounter);
}
}
void CopyValueFrom(const HistoryEntryCounterForBrowsingContext& aOther) {
if (mHasModified) {
--(*mCounter);
}
mCounter = aOther.mCounter;
mHasModified = false;
}
HistoryEntryCounterForBrowsingContext& operator=(
const HistoryEntryCounterForBrowsingContext& aOther) = delete;
HistoryEntryCounterForBrowsingContext& operator++() {
mHasModified = true;
++(*mCounter);
return *this;
}
operator uint32_t() const { return *mCounter; }
bool Modified() { return mHasModified; }
void SetModified(bool aModified) { mHasModified = aModified; }
void Reset() {
if (mHasModified) {
--(*mCounter);
}
mCounter = new RefCountedCounter();
mHasModified = false;
}
private:
class RefCountedCounter {
public:
NS_INLINE_DECL_REFCOUNTING(
mozilla::dom::HistoryEntryCounterForBrowsingContext::RefCountedCounter)
RefCountedCounter& operator++() {
++mCounter;
return *this;
}
RefCountedCounter& operator--() {
--mCounter;
return *this;
}
operator uint32_t() const { return mCounter; }
private:
~RefCountedCounter() = default;
uint32_t mCounter = 0;
};
RefPtr<RefCountedCounter> mCounter;
bool mHasModified;
};
// SessionHistoryEntry is used to store session history data in the parent
// process. It holds a SessionHistoryInfo, some state shared amongst multiple
// SessionHistoryEntries, a parent and children.
@ -271,6 +357,16 @@ class SessionHistoryEntry : public nsISHEntry {
const nsID& DocshellID() const;
HistoryEntryCounterForBrowsingContext& BCHistoryLength() {
return mBCHistoryLength;
}
void SetBCHistoryLength(HistoryEntryCounterForBrowsingContext& aCounter) {
mBCHistoryLength.CopyValueFrom(aCounter);
}
void ClearBCHistoryLength() { mBCHistoryLength.Reset(); }
void SetIsDynamicallyAdded(bool aDynamic);
// Get an entry based on LoadingSessionHistoryInfo's mLoadId. Parent process
@ -292,6 +388,8 @@ class SessionHistoryEntry : public nsISHEntry {
bool mForInitialLoad = false;
HistoryEntryCounterForBrowsingContext mBCHistoryLength;
static nsDataHashtable<nsUint64HashKey, SessionHistoryEntry*>* sLoadIdToEntry;
};

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

@ -12,6 +12,7 @@
#include "nsCOMArray.h"
#include "nsComponentManagerUtils.h"
#include "nsDocShell.h"
#include "nsHashKeys.h"
#include "nsIContentViewer.h"
#include "nsIDocShell.h"
#include "nsDocShellLoadState.h"
@ -23,6 +24,7 @@
#include "nsIURI.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsDataHashtable.h"
#include "nsSHEntry.h"
#include "SessionHistoryEntry.h"
#include "nsTArray.h"
@ -822,6 +824,9 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) {
mEntries.TruncateLength(mIndex + 1);
mEntries.AppendElement(aSHEntry);
mIndex++;
if (mIndex > 0) {
UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false);
}
// Purge History list if it is too long
if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
@ -970,6 +975,29 @@ void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
*aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
}
static void MarkAsInitialEntry(
SessionHistoryEntry* aEntry,
nsDataHashtable<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
if (!aEntry->BCHistoryLength().Modified()) {
++(aEntry->BCHistoryLength());
}
aHashtable.Put(aEntry->DocshellID(), aEntry);
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
MarkAsInitialEntry(entry, aHashtable);
}
}
}
static void ClearEntries(SessionHistoryEntry* aEntry) {
aEntry->ClearBCHistoryLength();
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
ClearEntries(entry);
}
}
}
NS_IMETHODIMP
nsSHistory::PurgeHistory(int32_t aNumEntries) {
if (Length() <= 0 || aNumEntries <= 0) {
@ -982,6 +1010,37 @@ nsSHistory::PurgeHistory(int32_t aNumEntries) {
NOTIFY_LISTENERS(OnHistoryPurge, ());
// Set all the entries hanging of the first entry that we keep
// (mEntries[aNumEntries]) as being created as the result of a load
// (so contributing one to their BCHistoryLength).
nsDataHashtable<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
if (aNumEntries != Length()) {
nsCOMPtr<SessionHistoryEntry> she =
do_QueryInterface(mEntries[aNumEntries]);
if (she) {
MarkAsInitialEntry(she, docshellIDToEntry);
}
}
// Reset the BCHistoryLength of all the entries that we're removing to a new
// counter with value 0 while decreasing their contribution to a shared
// BCHistoryLength. The end result is that they don't contribute to the
// BCHistoryLength of any other entry anymore.
for (int32_t i = 0; i < aNumEntries; ++i) {
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]);
if (she) {
ClearEntries(she);
}
}
if (mRootBC) {
mRootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
Unused << aBC->SetHistoryEntryCount(
entry ? uint32_t(entry->BCHistoryLength()) : 0);
});
}
// Remove the first `aNumEntries` entries.
mEntries.RemoveElementsAt(0, aNumEntries);
@ -1550,6 +1609,17 @@ bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
SHistoryChangeNotifier change(this);
if (IsSameTree(root1, root2)) {
if (aIndex < compareIndex) {
// If we're removing the entry with the lower index we need to move its
// BCHistoryLength to the entry we're keeping. If we're removing the entry
// with the higher index then it shouldn't have a modified
// BCHistoryLength.
UpdateEntryLength(root1, root2, true);
}
nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(root1);
if (she) {
ClearEntries(she);
}
mEntries.RemoveElementAt(aIndex);
// FIXME Bug 1546350: Reimplement history listeners.
@ -1971,3 +2041,60 @@ nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
return true;
}
static void CollectEntries(
nsDataHashtable<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
SessionHistoryEntry* aEntry) {
aHashtable.Put(aEntry->DocshellID(), aEntry);
for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
if (entry) {
CollectEntries(aHashtable, entry);
}
}
}
static void UpdateEntryLength(
nsDataHashtable<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
SessionHistoryEntry* aNewEntry, bool aMove) {
SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID());
if (oldEntry) {
MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove ||
!aNewEntry->BCHistoryLength().Modified());
aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength());
if (oldEntry->GetID() != aNewEntry->GetID()) {
MOZ_ASSERT(!aMove);
// If we have a new id then aNewEntry was created for a new load, so
// record that in BCHistoryLength.
++aNewEntry->BCHistoryLength();
} else if (aMove) {
// We're moving the BCHistoryLength from the old entry to the new entry,
// so we need to let the old entry know that it's not contributing to its
// BCHistoryLength, and the new one that it does if the old one was
// before.
aNewEntry->BCHistoryLength().SetModified(
oldEntry->BCHistoryLength().Modified());
oldEntry->BCHistoryLength().SetModified(false);
}
}
for (const RefPtr<SessionHistoryEntry>& entry : aNewEntry->Children()) {
if (entry) {
UpdateEntryLength(aHashtable, entry, aMove);
}
}
}
void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
bool aMove) {
nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
if (!oldSHE || !newSHE) {
return;
}
nsDataHashtable<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
CollectEntries(docshellIDToEntry, oldSHE);
::UpdateEntryLength(docshellIDToEntry, newSHE, aMove);
}

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

@ -261,6 +261,9 @@ class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
nsISHEntry* aOldEntry,
nsISHEntry* aNewEntry);
void UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
bool aMove);
protected:
bool mHasOngoingUpdate;
bool mIsRemote;

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

@ -0,0 +1,37 @@
<html>
<head>
<script>
async function wait() {
return opener.SpecialPowers.spawnChrome([], function() { /*dummy */ });
}
async function run() {
var counter = 0;
while(true) {
// Push new history entries until we hit the limit and start removing
// entries from the front.
var len = history.length;
document.getElementById("ifr1").contentWindow.history.pushState(++counter, null, null);
await wait();
if (len == history.length) {
opener.ok(true, "Found max length " + len);
document.getElementById("ifr2").contentWindow.history.pushState(++counter, null, null);
await wait();
document.getElementById("ifr1").contentWindow.history.pushState(++counter, null, null);
await wait();
opener.is(len, history.length, "Adding session history entries in different iframe should behave the same way");
// This should remove all the history entries for ifr1, leaving just
// the ones for ifr2.
document.getElementById("ifr1").remove();
opener.is(history.length, 2, "Should have entries for the initial load and the pushState for ifr2");
opener.finishTest();
break;
}
}
}
</script>
</head>
<body onload="setTimeout(run)">
<iframe src="blank.html" id="ifr1"></iframe>
<iframe src="blank.html" id="ifr2"></iframe>
</body>
</html>

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

@ -96,6 +96,8 @@ skip-if = fission # bug 1666602
skip-if = verify && (os == 'mac') && debug && webrender # Hit MOZ_CRASH(Shutdown too long, probably frozen, causing a crash.) bug 1677545
[test_dynamic_frame_forward_back.html]
[test_sessionhistory_document_write.html]
[test_sessionhistory_iframe_removal.html]
support-files = file_sessionhistory_iframe_removal.html
[test_session_history_entry_cleanup.html]
[test_fragment_handling_during_load.html]
[test_nested_frames.html]

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

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=
-->
<head>
<title>Test for Session history + document.write</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="runTest()">
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var testWindow;
function runTest() {
testWindow = window.open("file_sessionhistory_iframe_removal.html", "", "width=360,height=480");
}
function finishTest() {
testWindow.close();
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

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

@ -1881,7 +1881,21 @@ void nsFrameLoader::StartDestroy(bool aForProcessSwitch) {
browsingContext->Top()->GetChildSessionHistory();
if (childSHistory) {
if (mozilla::SessionHistoryInParent()) {
browsingContext->RemoveFromSessionHistory();
uint32_t addedEntries = 0;
browsingContext->PreOrderWalk([&addedEntries](BrowsingContext* aBC) {
// The initial load doesn't increase history length.
addedEntries += aBC->GetHistoryEntryCount() - 1;
});
nsID changeID = {};
if (addedEntries > 0) {
ChildSHistory* shistory =
browsingContext->Top()->GetChildSessionHistory();
if (shistory) {
changeID = shistory->AddPendingHistoryChange(0, -addedEntries);
}
}
browsingContext->RemoveFromSessionHistory(changeID);
} else {
AutoTArray<nsID, 16> ids({browsingContext->GetHistoryID()});
childSHistory->LegacySHistory()->RemoveEntries(

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

@ -7419,9 +7419,9 @@ ContentParent::RecvRemoveDynEntriesFromActiveSessionHistoryEntry(
}
mozilla::ipc::IPCResult ContentParent::RecvRemoveFromSessionHistory(
const MaybeDiscarded<BrowsingContext>& aContext) {
const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aChangeID) {
if (!aContext.IsDiscarded()) {
aContext.get_canonical()->RemoveFromSessionHistory();
aContext.get_canonical()->RemoveFromSessionHistory(aChangeID);
}
return IPC_OK();
}

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

@ -1407,7 +1407,7 @@ class ContentParent final
const MaybeDiscarded<BrowsingContext>& aContext);
mozilla::ipc::IPCResult RecvRemoveFromSessionHistory(
const MaybeDiscarded<BrowsingContext>& aContext);
const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aChangeID);
mozilla::ipc::IPCResult RecvHistoryReload(
const MaybeDiscarded<BrowsingContext>& aContext,

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

@ -1737,7 +1737,7 @@ parent:
MaybeDiscardedBrowsingContext aContext);
async RemoveFromSessionHistory(
MaybeDiscardedBrowsingContext aContext);
MaybeDiscardedBrowsingContext aContext, nsID changeID);
both:
async ScriptError(nsString message, nsString sourceName, nsString sourceLine,

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

@ -1,3 +0,0 @@
[joint-session-history-remove-iframe.html]
disabled:
if fission: https://bugzilla.mozilla.org/show_bug.cgi?id=1656208

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

@ -1,3 +0,0 @@
[discard_iframe_history_1.html]
disabled:
if fission: https://bugzilla.mozilla.org/show_bug.cgi?id=1656208

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

@ -1,3 +0,0 @@
[discard_iframe_history_2.html]
disabled:
if fission: https://bugzilla.mozilla.org/show_bug.cgi?id=1656208

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

@ -1,3 +0,0 @@
[discard_iframe_history_3.html]
disabled:
if fission: https://bugzilla.mozilla.org/show_bug.cgi?id=1656208

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

@ -1,3 +0,0 @@
[discard_iframe_history_4.html]
disabled:
if fission: https://bugzilla.mozilla.org/show_bug.cgi?id=1656208