Bug 1515073 - Part 1 - Add nsISHEntry::hasUserInteraction. r=peterv

Differential Revision: https://phabricator.services.mozilla.com/D27585
This commit is contained in:
Johann Hofmann 2020-06-09 14:48:38 +00:00
parent 9b32c2661d
commit 2121e27531
10 changed files with 157 additions and 7 deletions

View File

@ -19,6 +19,16 @@ class WindowGlobalInit;
class BrowsingContextGroup;
#define MOZ_EACH_WC_FIELD(FIELD) \
/* Whether the SHEntry associated with the current top-level \
* window has already seen user interaction. \
* As such, this will be reset to false when a new SHEntry is \
* created without changing the WC (e.g. when using pushState or \
* sub-frame navigation) \
* This flag is set for optimization purposes, to avoid \
* having to get the top SHEntry and update it on every \
* user interaction. \
* This is only meaningful on the top-level WC. */ \
FIELD(SHEntryHasUserInteraction, bool) \
FIELD(CookieBehavior, Maybe<uint32_t>) \
FIELD(IsOnContentBlockingAllowList, bool) \
/* Whether the given window hierarchy is third party. See \
@ -147,6 +157,10 @@ class WindowContext : public nsISupports, public nsWrapperCache {
ContentParent* aSource);
bool CanSet(FieldIndex<IDX_AutoplayPermission>, const uint32_t& aValue,
ContentParent* aSource);
bool CanSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
const bool& aSHEntryHasUserInteraction, ContentParent* aSource) {
return true;
}
// Overload `DidSet` to get notifications for a particular field being set.
//

View File

@ -10750,8 +10750,24 @@ nsresult nsDocShell::AddToSessionHistory(
if (loadedEntryIndex.isSome()) {
mLoadedEntryIndex = loadedEntryIndex.value();
}
// aCloneChildren implies that we are retaining the same document, thus we
// need to signal to the top WC that the new SHEntry may receive a fresh
// user interaction flag.
if (aCloneChildren) {
WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
if (topWc) {
topWc->SetSHEntryHasUserInteraction(false);
}
}
} else {
// This is a subframe.
// This is a subframe, make sure that this new SHEntry will be
// marked with user interaction.
WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
if (topWc) {
topWc->SetSHEntryHasUserInteraction(false);
}
if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren);
}

View File

@ -130,6 +130,18 @@ SessionHistoryEntry::SetIsSubFrame(bool aIsSubFrame) {
return NS_OK;
}
NS_IMETHODIMP
SessionHistoryEntry::GetHasUserInteraction(bool* aFlag) {
MOZ_CRASH("Not needed in the parent process?");
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
SessionHistoryEntry::SetHasUserInteraction(bool aFlag) {
MOZ_CRASH("Not needed in the parent process?");
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
SessionHistoryEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
nsCOMPtr<nsIReferrerInfo> referrerInfo = mInfo->mReferrerInfo;

View File

@ -83,6 +83,14 @@ interface nsISHEntry : nsISupports
*/
[infallible] attribute boolean isSubFrame;
/**
* Whether the user interacted with the page while this entry was active.
* This includes interactions with subframe documents associated with
* child entries that are rooted at this entry.
* This field will only be set on top-level entries.
*/
[infallible] attribute boolean hasUserInteraction;
/** Referrer Info*/
[infallible] attribute nsIReferrerInfo referrerInfo;

View File

@ -44,7 +44,8 @@ nsSHEntry::nsSHEntry(nsISHistory* aSHistory)
mIsSrcdocEntry(false),
mScrollRestorationIsManual(false),
mLoadedInThisProcess(false),
mPersist(true) {}
mPersist(true),
mHasUserInteraction(false) {}
nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
: mShared(aOther.mShared),
@ -70,7 +71,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
mIsSrcdocEntry(aOther.mIsSrcdocEntry),
mScrollRestorationIsManual(false),
mLoadedInThisProcess(aOther.mLoadedInThisProcess),
mPersist(aOther.mPersist) {}
mPersist(aOther.mPersist),
mHasUserInteraction(false) {}
nsSHEntry::~nsSHEntry() {
// Null out the mParent pointers on all our kids.
@ -286,6 +288,31 @@ nsSHEntry::SetIsSubFrame(bool aFlag) {
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetHasUserInteraction(bool* aFlag) {
// We can't assert that this getter isn't accessed only on root
// entries because there's JS code that will iterate over entries
// for serialization etc., so let's assert the next best thing.
MOZ_ASSERT(!mParent || !mHasUserInteraction,
"User interaction can only be set on root entries");
*aFlag = mHasUserInteraction;
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetHasUserInteraction(bool aFlag) {
// The back button and menulist deal with root/top-level
// session history entries, thus we annotate only the root entry.
if (!mParent) {
mHasUserInteraction = aFlag;
} else {
nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
root->SetHasUserInteraction(aFlag);
}
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::GetCacheKey(uint32_t* aResult) {
*aResult = mShared->mCacheKey;
@ -360,6 +387,8 @@ nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle,
// all subframe navigations, sets the flag to true.
mShared->mIsFrameNavigation = false;
mHasUserInteraction = false;
mShared->mExpired = aExpired;
mIsSrcdocEntry = aSrcdocEntry;

View File

@ -63,6 +63,7 @@ class nsSHEntry : public nsISHEntry {
bool mScrollRestorationIsManual;
bool mLoadedInThisProcess;
bool mPersist;
bool mHasUserInteraction;
};
#endif /* nsSHEntry_h */

View File

@ -523,6 +523,7 @@ nsresult nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef,
uint32_t cloneID = aCloneRef->GetID();
rv = nsSHistory::CloneAndReplace(currentHE, aBC, cloneID, aNewEntry,
aCloneChildren, aNextEntry);
if (NS_SUCCEEDED(rv)) {
rv = AddEntry(*aNextEntry, true);
}

View File

@ -93,6 +93,8 @@
#include "nsINSSErrorsService.h"
#include "nsISocketProvider.h"
#include "nsISiteSecurityService.h"
#include "nsISHEntry.h"
#include "nsSHistory.h"
#include "mozilla/PermissionDelegateHandler.h"
#include "mozilla/AsyncEventDispatcher.h"
@ -15061,6 +15063,36 @@ void Document::ReportHasScrollLinkedEffect() {
nsContentUtils::eLAYOUT_PROPERTIES, "ScrollLinkedEffectFound2");
}
void Document::SetSHEntryHasUserInteraction(bool aHasInteraction) {
nsPIDOMWindowInner* inner = GetInnerWindow();
if (NS_WARN_IF(!inner)) {
return;
}
WindowContext* wc = inner->GetWindowContext();
if (NS_WARN_IF(!wc)) {
return;
}
WindowContext* topWc = wc->TopWindowContext();
topWc->SetSHEntryHasUserInteraction(aHasInteraction);
}
bool Document::GetSHEntryHasUserInteraction() {
nsPIDOMWindowInner* inner = GetInnerWindow();
if (NS_WARN_IF(!inner)) {
return false;
}
WindowContext* wc = inner->GetWindowContext();
if (NS_WARN_IF(!wc)) {
return false;
}
WindowContext* topWc = wc->TopWindowContext();
return topWc->GetSHEntryHasUserInteraction();
}
void Document::SetUserHasInteracted() {
MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug,
("Document %p has been interacted by user.", this));
@ -15068,6 +15100,27 @@ void Document::SetUserHasInteracted() {
// We maybe need to update the user-interaction permission.
MaybeStoreUserInteractionAsPermission();
// For purposes of reducing irrelevant session history entries on
// the back button, we annotate entries with whether they had user
// interaction. This is gated on its own flag on the WindowContext
// (instead of mUserHasInteracted) to account for the fact that multiple
// top-level SH entries can be associated with the same document.
// Thus, whenever we create a new SH entry for this document,
// this flag is reset.
if (!GetSHEntryHasUserInteraction()) {
nsIDocShell* docShell = this->GetDocShell();
if (docShell) {
nsCOMPtr<nsISHEntry> currentEntry;
bool oshe;
nsresult rv =
docShell->GetCurrentSHEntry(getter_AddRefs(currentEntry), &oshe);
if (!NS_WARN_IF(NS_FAILED(rv)) && currentEntry) {
currentEntry->SetHasUserInteraction(true);
}
}
SetSHEntryHasUserInteraction(true);
}
if (mUserHasInteracted) {
return;
}

View File

@ -3742,12 +3742,15 @@ class Document : public nsINode,
private:
void DoCacheAllKnownLangPrefs();
void RecomputeLanguageFromCharset();
bool GetSHEntryHasUserInteraction();
public:
void SetMayNeedFontPrefsUpdate() { mMayNeedFontPrefsUpdate = true; }
bool MayNeedFontPrefsUpdate() { return mMayNeedFontPrefsUpdate; }
void SetSHEntryHasUserInteraction(bool aHasInteraction);
already_AddRefed<nsAtom> GetContentLanguageAsAtomForStyle() const;
already_AddRefed<nsAtom> GetLanguageForStyle() const;

View File

@ -276,6 +276,8 @@ var SessionHistoryInternal = {
);
}
entry.hasUserInteraction = shEntry.hasUserInteraction;
if (shEntry.triggeringPrincipal) {
entry.triggeringPrincipal_base64 = E10SUtils.serializePrincipal(
shEntry.triggeringPrincipal
@ -375,10 +377,21 @@ var SessionHistoryInternal = {
continue;
}
let persist = "persist" in entry ? entry.persist : true;
history.addEntry(
this.deserializeEntry(entry, idMap, docIdentMap, history),
persist
);
let shEntry = this.deserializeEntry(entry, idMap, docIdentMap, history);
// To enable a smooth migration, we treat values of null/undefined as having
// user interaction (because we don't want to hide all session history that was
// added before we started recording user interaction).
//
// This attribute is only set on top-level SH history entries, so we set it
// outside of deserializeEntry since that is called recursively.
if (entry.hasUserInteraction == undefined) {
shEntry.hasUserInteraction = true;
} else {
shEntry.hasUserInteraction = entry.hasUserInteraction;
}
history.addEntry(shEntry, persist);
}
// Select the right history entry.