From 101955fa30e04d59920f20ad096da51faf40091f Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Tue, 2 Mar 2021 09:44:12 +0000 Subject: [PATCH] Bug 1689601, try to use bfcache for top level pages, r=peterv Differential Revision: https://phabricator.services.mozilla.com/D105238 --- docshell/base/BrowsingContext.cpp | 19 ++++ docshell/base/BrowsingContext.h | 6 +- docshell/base/CanonicalBrowsingContext.cpp | 33 ++++-- docshell/base/CanonicalBrowsingContext.h | 1 + docshell/base/nsDocShell.cpp | 59 ++++++++++ docshell/base/nsDocShell.h | 2 + docshell/shistory/SessionHistoryEntry.cpp | 3 +- docshell/shistory/nsSHistory.cpp | 122 ++++++++++++++++++++- docshell/shistory/nsSHistory.h | 1 + dom/base/Document.cpp | 17 +-- dom/base/nsFrameLoader.cpp | 19 +++- dom/base/nsFrameLoader.h | 4 +- dom/base/nsFrameLoaderOwner.cpp | 97 +++++++++++++--- dom/base/nsFrameLoaderOwner.h | 11 ++ dom/ipc/BrowserParent.h | 1 + dom/ipc/ContentChild.cpp | 69 ++++++++++++ dom/ipc/ContentChild.h | 5 + dom/ipc/PContent.ipdl | 4 + netwerk/ipc/DocumentChannelChild.cpp | 10 +- netwerk/ipc/DocumentLoadListener.cpp | 53 +++++++++ 20 files changed, 498 insertions(+), 38 deletions(-) diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp index 3eb6d56a0081..23ab42014d24 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -2621,6 +2621,25 @@ void BrowsingContext::DidSet(FieldIndex) { }); } +bool BrowsingContext::CanSet(FieldIndex, bool, + ContentParent* aSource) { + return IsTop() && !aSource && StaticPrefs::fission_bfcacheInParent(); +} + +void BrowsingContext::DidSet(FieldIndex) { + MOZ_RELEASE_ASSERT(StaticPrefs::fission_bfcacheInParent()); + MOZ_DIAGNOSTIC_ASSERT(IsTop()); + + const bool isInBFCache = GetIsInBFCache(); + PreOrderWalk([&](BrowsingContext* aContext) { + nsCOMPtr shell = aContext->GetDocShell(); + if (shell) { + static_cast(shell.get()) + ->FirePageHideShowNonRecursive(!isInBFCache); + } + }); +} + void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform, ErrorResult& aRv) { Top()->SetPlatformOverride(aPlatform, aRv); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h index 5a4255af462a..99c5c68ac93f 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -197,7 +197,8 @@ enum class ExplicitActiveStatus : uint8_t { FIELD(HasMainMediaController, bool) \ /* The number of entries added to the session history because of this \ * browsing context. */ \ - FIELD(HistoryEntryCount, uint32_t) + FIELD(HistoryEntryCount, uint32_t) \ + FIELD(IsInBFCache, bool) // BrowsingContext, in this context, is the cross process replicated // environment in which information about documents is stored. In @@ -1035,6 +1036,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { void DidSet(FieldIndex, float aOldValue); void DidSet(FieldIndex); + bool CanSet(FieldIndex, bool, ContentParent* aSource); + void DidSet(FieldIndex); + // True if the process attemping to set field is the same as the owning // process. Deprecated. New code that might use this should generally be moved // to WindowContext or be settable only by the parent process. diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp index 45c64b11fd2b..8d8c7490bef5 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -24,6 +24,7 @@ #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/DocumentLoadListener.h" #include "mozilla/NullPrincipal.h" +#include "mozilla/StaticPrefs_fission.h" #include "nsIWebNavigation.h" #include "mozilla/MozPromiseInlines.h" #include "nsDocShell.h" @@ -158,7 +159,8 @@ void CanonicalBrowsingContext::MaybeAddAsProgressListener( void CanonicalBrowsingContext::ReplacedBy(CanonicalBrowsingContext* aNewContext, const RemotenessChangeState& aState) { - MOZ_ASSERT(!aNewContext->EverAttached()); + MOZ_ASSERT(!aNewContext->mWebProgress); + MOZ_ASSERT(!aNewContext->mSessionHistory); MOZ_ASSERT(IsTop() && aNewContext->IsTop()); if (mStatusFilter) { mStatusFilter->RemoveProgressListener(mWebProgress); @@ -170,8 +172,20 @@ void CanonicalBrowsingContext::ReplacedBy(CanonicalBrowsingContext* aNewContext, aNewContext->mFields.SetWithoutSyncing( GetExplicitActive()); + // XXXBFCache name handling is still a bit broken in Fission in general, + // at least in case name should be cleared. + if (aState.mTryUseBFCache) { + aNewContext->mFields.SetWithoutSyncing(GetName()); + aNewContext->mFields.SetWithoutSyncing( + GetHasLoadedNonInitialDocument()); + } + if (mSessionHistory) { mSessionHistory->SetBrowsingContext(aNewContext); + if (StaticPrefs::fission_bfcacheInParent()) { + // XXXBFCache Should we clear the epoch always? + mSessionHistory->SetEpoch(0, Nothing()); + } mSessionHistory.swap(aNewContext->mSessionHistory); RefPtr childSHistory = ForgetChildSHistory(); aNewContext->SetChildSHistory(childSHistory); @@ -307,6 +321,11 @@ SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() { return mActiveEntry; } +void CanonicalBrowsingContext::SetActiveSessionHistoryEntry( + SessionHistoryEntry* aEntry) { + mActiveEntry = aEntry; +} + bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) { // XXX Should we check also loading entries? return aEntry && mActiveEntry == aEntry; @@ -1511,6 +1530,12 @@ bool CanonicalBrowsingContext::SupportsLoadingInParent( return false; } + // Session-history-in-parent implementation relies currently on getting a + // round trip through a child process. + if (aLoadState->LoadIsFromSessionHistory()) { + return false; + } + // DocumentChannel currently only supports connecting channels into the // content process, so we can only support schemes that will always be loaded // there for now. Restrict to just http(s) for simplicity. @@ -1584,12 +1609,6 @@ bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent( return false; } - // Session-history-in-parent implementation relies currently on getting a - // round trip through a child process. - if (aLoadState->LoadIsFromSessionHistory()) { - return false; - } - // If we successfully open the DocumentChannel, then it'll register // itself using aLoadIdentifier and be kept alive until it completes // loading. diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h index 6d93ecff9dff..84df81fa0b77 100644 --- a/docshell/base/CanonicalBrowsingContext.h +++ b/docshell/base/CanonicalBrowsingContext.h @@ -120,6 +120,7 @@ class CanonicalBrowsingContext final : public BrowsingContext { nsISHistory* GetSessionHistory(); SessionHistoryEntry* GetActiveSessionHistoryEntry(); + void SetActiveSessionHistoryEntry(SessionHistoryEntry* aEntry); UniquePtr CreateLoadingSessionHistoryEntryForLoad( nsDocShellLoadState* aLoadState, nsIChannel* aChannel); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index f0a957b31fb2..0209c1150cb8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -1195,6 +1195,64 @@ void nsDocShell::FirePageHideNotificationInternal( } } +void nsDocShell::FirePageHideShowNonRecursive(bool aShow) { + MOZ_ASSERT(StaticPrefs::fission_bfcacheInParent()); + + if (!mContentViewer) { + return; + } + + // Emulate what non-SHIP BFCache does too. In pageshow case + // add and remove a request and before that call SetCurrentURI to get + // the location change notification. + // For pagehide, set mFiredUnloadEvent to true, so that unload doesn't fire. + nsCOMPtr contentViewer(mContentViewer); + if (aShow) { + mFiredUnloadEvent = false; + RefPtr doc = contentViewer->GetDocument(); + if (doc) { + if (mBrowsingContext->IsTop()) { + doc->NotifyPossibleTitleChange(false); + if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal()) { + // XXXBFCache Resume doesn't go through oop iframes. + mScriptGlobal->GetCurrentInnerWindowInternal()->Thaw(); + } + } + nsCOMPtr channel = doc->GetChannel(); + if (channel) { + SetCurrentURI(doc->GetDocumentURI(), channel, true, 0); + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + RefPtr presShell = GetPresShell(); + if (presShell) { + // XXXBFcache Thaw doesn't deal with OOP iframes. + presShell->Thaw(); + } + } + } else if (!mFiredUnloadEvent) { + // XXXBFCache check again that the page can enter bfcache. + // XXXBFCache should mTiming->NotifyUnloadEventStart()/End() be called here? + mFiredUnloadEvent = true; + contentViewer->PageHide(false); + + if (mBrowsingContext->IsTop()) { + if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal()) { + // XXXBFCache Resume doesn't go through oop iframes. + mScriptGlobal->GetCurrentInnerWindowInternal()->Freeze(); + } + } + RefPtr presShell = GetPresShell(); + if (presShell) { + // XXXBFcache Freeze doesn't deal with OOP iframes. + presShell->Freeze(); + } + } +} + nsresult nsDocShell::Dispatch(TaskCategory aCategory, already_AddRefed&& aRunnable) { nsCOMPtr runnable(aRunnable); @@ -6898,6 +6956,7 @@ bool nsDocShell::CanSavePresentation(uint32_t aLoadType, // Only save presentation for "normal" loads and link loads. Anything else // probably wants to refetch the page, so caching the old presentation // would be incorrect. + // XXXBFCache in parent needs something like this! if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 50c1e78a4d98..2a43dc91e07d 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -937,6 +937,8 @@ class nsDocShell final : public nsDocLoader, void FirePageHideNotificationInternal(bool aIsUnload, bool aSkipCheckingDynEntries); + void FirePageHideShowNonRecursive(bool aShow); + nsresult Dispatch(mozilla::TaskCategory aCategory, already_AddRefed&& aRunnable); diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp index 0106bfd22a0f..4a1de5fbe24d 100644 --- a/docshell/shistory/SessionHistoryEntry.cpp +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -1348,7 +1348,8 @@ SHEntrySharedParentState* SessionHistoryEntry::SharedInfo() const { void SessionHistoryEntry::SetFrameLoader(nsFrameLoader* aFrameLoader) { MOZ_ASSERT_IF(aFrameLoader, !SharedInfo()->mFrameLoader); - MOZ_RELEASE_ASSERT(StaticPrefs::fission_bfcacheInParent()); + // If the pref is disabled, we still allow evicting the existing entries. + MOZ_RELEASE_ASSERT(!aFrameLoader || StaticPrefs::fission_bfcacheInParent()); SharedInfo()->mFrameLoader = aFrameLoader; if (aFrameLoader) { // When a new frameloader is stored, try to evict some older diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp index 468ceaf73c5a..8d99d078069e 100644 --- a/docshell/shistory/nsSHistory.cpp +++ b/docshell/shistory/nsSHistory.cpp @@ -12,6 +12,7 @@ #include "nsCOMArray.h" #include "nsComponentManagerUtils.h" #include "nsDocShell.h" +#include "nsFrameLoaderOwner.h" #include "nsHashKeys.h" #include "nsIContentViewer.h" #include "nsIDocShell.h" @@ -31,12 +32,15 @@ #include "prsystem.h" #include "mozilla/Attributes.h" +#include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" #include "mozilla/LinkedList.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "nsIWebNavigation.h" @@ -51,12 +55,16 @@ using namespace mozilla::dom; "browser.sessionhistory.max_total_viewers" #define CONTENT_VIEWER_TIMEOUT_SECONDS \ "browser.sessionhistory.contentViewerTimeout" +// Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when +// the pref is changed. +#define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent" // Default this to time out unused content viewers after 30 minutes #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) -static const char* kObservedPrefs[] = { - PREF_SHISTORY_SIZE, PREF_SHISTORY_MAX_TOTAL_VIEWERS, nullptr}; +static const char* kObservedPrefs[] = {PREF_SHISTORY_SIZE, + PREF_SHISTORY_MAX_TOTAL_VIEWERS, + PREF_FISSION_BFCACHEINPARENT, nullptr}; static int32_t gHistoryMaxSize = 50; // List of all SHistory objects, used for content viewer cache eviction @@ -76,6 +84,7 @@ LazyLogModule gSHistoryLog("nsSHistory"); #define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format) extern mozilla::LazyLogModule gPageCacheLog; +extern mozilla::LazyLogModule gSHIPBFCacheLog; // This macro makes it easier to print a log message which includes a URI's // spec. Example use: @@ -342,7 +351,8 @@ uint32_t nsSHistory::CalcMaxTotalViewers() { // static void nsSHistory::UpdatePrefs() { Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); - if (mozilla::SessionHistoryInParent()) { + if (mozilla::SessionHistoryInParent() && + !StaticPrefs::fission_bfcacheInParent()) { sHistoryMaxTotalViewers = 0; return; } @@ -1165,10 +1175,114 @@ nsSHistory::EvictAllContentViewers() { return NS_OK; } +/* static */ +void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) { + if (mozilla::SessionHistoryInParent() && + StaticPrefs::fission_bfcacheInParent() && + aLoadEntry.mBrowsingContext->IsTop()) { + MOZ_ASSERT(XRE_IsParentProcess()); + RefPtr loadState = aLoadEntry.mLoadState; + RefPtr canonicalBC = + aLoadEntry.mBrowsingContext->Canonical(); + nsCOMPtr she = do_QueryInterface(loadState->SHEntry()); + nsCOMPtr currentShe = + canonicalBC->GetActiveSessionHistoryEntry(); + MOZ_ASSERT(she); + RefPtr frameLoader = she->GetFrameLoader(); + if (canonicalBC->Group()->Toplevels().Length() == 1 && frameLoader && + (!currentShe || she->SharedInfo() != currentShe->SharedInfo())) { + nsTArray> + canSavePromises; + canonicalBC->Group()->EachParent([&](ContentParent* aParent) { + RefPtr canSave = + aParent->SendCanSavePresentation(canonicalBC, Nothing()); + canSavePromises.AppendElement(canSave); + }); + + // Check if the current page can enter bfcache. + PContentParent::CanSavePresentationPromise::All( + GetCurrentSerialEventTarget(), canSavePromises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [canonicalBC, loadState, she](const nsTArray aCanSaves) { + bool canSave = !aCanSaves.Contains(false); + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsSHistory::LoadURIOrBFCache " + "saving presentation=%i", + canSave)); + + nsCOMPtr frameLoaderOwner = + do_QueryInterface(canonicalBC->GetEmbedderElement()); + if (frameLoaderOwner) { + RefPtr fl = she->GetFrameLoader(); + if (fl) { + she->SetFrameLoader(nullptr); + RefPtr loadingBC = + fl->GetMaybePendingBrowsingContext(); + if (loadingBC) { + RefPtr currentFrameLoader = + frameLoaderOwner->GetFrameLoader(); + // The current page can be bfcached, store the + // nsFrameLoader in the current SessionHistoryEntry. + if (canSave && + canonicalBC->GetActiveSessionHistoryEntry()) { + canonicalBC->GetActiveSessionHistoryEntry() + ->SetFrameLoader(currentFrameLoader); + Unused << canonicalBC->SetIsInBFCache(true); + } + + // ReplacedBy will swap the entry back. + canonicalBC->SetActiveSessionHistoryEntry(she); + loadingBC->Canonical()->SetActiveSessionHistoryEntry( + nullptr); + RemotenessChangeState state; + canonicalBC->ReplacedBy(loadingBC->Canonical(), state); + frameLoaderOwner->ReplaceFrameLoader(fl); + + // The old page can't be stored in the bfcache, + // destroy the nsFrameLoader. + if (!canSave && currentFrameLoader) { + currentFrameLoader->Destroy(); + } + // The current active entry should not store + // nsFrameLoader. + loadingBC->Canonical() + ->GetSessionHistory() + ->UpdateIndex(); + loadingBC->Canonical()->HistoryCommitIndexAndLength(); + Unused << loadingBC->SetIsInBFCache(false); + // ResetSHEntryHasUserInteractionCache(); ? + // browser.navigation.requireUserInteraction is still + // disabled everywhere. + return; + } + } + } + + // Fall back to do a normal load. + canonicalBC->LoadURI(loadState, false); + }, + [canonicalBC, loadState](mozilla::ipc::ResponseRejectReason) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsSHistory::LoadURIOrBFCache " + "error in trying to save presentation")); + canonicalBC->LoadURI(loadState, false); + }); + return; + } + if (frameLoader) { + she->SetFrameLoader(nullptr); + frameLoader->Destroy(); + } + } + + aLoadEntry.mBrowsingContext->LoadURI(aLoadEntry.mLoadState, false); +} + /* static */ void nsSHistory::LoadURIs(nsTArray& aLoadResults) { for (LoadEntryResult& loadEntry : aLoadResults) { - loadEntry.mBrowsingContext->LoadURI(loadEntry.mLoadState, false); + LoadURIOrBFCache(loadEntry); } } diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h index a13d718b2195..fb1bdc586a79 100644 --- a/docshell/shistory/nsSHistory.h +++ b/docshell/shistory/nsSHistory.h @@ -154,6 +154,7 @@ class nsSHistory : public mozilla::LinkedListElement, }; static void LoadURIs(nsTArray& aLoadResults); + static void LoadURIOrBFCache(LoadEntryResult& aLoadEntry); // If this doesn't return an error then either aLoadResult is set to nothing, // in which case the caller should ignore the load, or it returns a valid diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 0a4d0c8a61aa..3edcc9b19fc0 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -113,6 +113,7 @@ #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_docshell.h" #include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPrefs_full_screen_api.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_network.h" @@ -10784,13 +10785,15 @@ bool Document::CanSavePresentation(nsIRequest* aNewRequest, } } - // BFCache is currently not compatible with remote subframes (bug 1609324) - if (RefPtr browsingContext = GetBrowsingContext()) { - for (auto& child : browsingContext->Children()) { - if (!child->IsInProcess()) { - aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES; - ret = false; - break; + if (!StaticPrefs::fission_bfcacheInParent()) { + // BFCache is currently not compatible with remote subframes (bug 1609324) + if (RefPtr browsingContext = GetBrowsingContext()) { + for (auto& child : browsingContext->Children()) { + if (!child->IsInProcess()) { + aBFCacheCombo |= BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES; + ret = false; + break; + } } } } diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 1ef64d7d7d6b..635144c8e30a 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -193,12 +193,17 @@ nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext, mWillChangeProcess(false), mObservingOwnerContent(false), mTabProcessCrashFired(false), - mNotifyingCrash(false) {} + mNotifyingCrash(false) { + nsCOMPtr owner = do_QueryInterface(aOwner); + owner->AttachFrameLoader(this); +} nsFrameLoader::~nsFrameLoader() { if (mMessageManager) { mMessageManager->Disconnect(); } + + MOZ_ASSERT(!mOwnerContent); MOZ_RELEASE_ASSERT(mDestroyCalled); } @@ -1870,6 +1875,9 @@ void nsFrameLoader::StartDestroy(bool aForProcessSwitch) { !doc->InUnlinkOrDeletion(); doc->SetSubDocumentFor(mOwnerContent, nullptr); MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved); + + nsCOMPtr owner = do_QueryInterface(mOwnerContent); + owner->FrameLoaderDestroying(this); SetOwnerContent(nullptr); } @@ -2057,8 +2065,17 @@ void nsFrameLoader::SetOwnerContent(Element* aContent) { mObservingOwnerContent = false; mOwnerContent->RemoveMutationObserver(this); } + + if (RefPtr owner = do_QueryObject(mOwnerContent)) { + owner->DeattachFrameLoader(this); + } + mOwnerContent = aContent; + if (RefPtr owner = do_QueryObject(mOwnerContent)) { + owner->AttachFrameLoader(this); + } + if (mSessionStoreListener && mOwnerContent) { // mOwnerContent will only be null when the frame loader is being destroyed, // so the session store listener will be destroyed along with it. diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h index d35c965f5c50..b03aa9eb113d 100644 --- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -19,6 +19,7 @@ #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" +#include "mozilla/LinkedList.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/Nullable.h" @@ -98,7 +99,8 @@ typedef struct _GtkWidget GtkWidget; class nsFrameLoader final : public nsStubMutationObserver, public mozilla::dom::ipc::MessageManagerCallback, - public nsWrapperCache { + public nsWrapperCache, + public mozilla::LinkedListElement { friend class AutoResetInShow; friend class AutoResetInFrameSwap; friend class nsFrameLoaderOwner; diff --git a/dom/base/nsFrameLoaderOwner.cpp b/dom/base/nsFrameLoaderOwner.cpp index 2e1d6cd78384..de8983a40820 100644 --- a/dom/base/nsFrameLoaderOwner.cpp +++ b/dom/base/nsFrameLoaderOwner.cpp @@ -11,6 +11,7 @@ #include "nsSubDocumentFrame.h" #include "nsQueryObject.h" #include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/Logging.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/FrameLoaderBinding.h" @@ -24,6 +25,8 @@ #include "mozilla/StaticPrefs_fission.h" #include "mozilla/EventStateManager.h" +extern mozilla::LazyLogModule gSHIPBFCacheLog; + using namespace mozilla; using namespace mozilla::dom; @@ -126,15 +129,36 @@ void nsFrameLoaderOwner::ChangeRemotenessCommon( // or want, so we use the initial (possibly pending) browsing context // directly, instead. bc = mFrameLoader->GetMaybePendingBrowsingContext(); - if (aContextType == ChangeRemotenessContextType::PRESERVE) { - mFrameLoader->SetWillChangeProcess(); + networkCreated = mFrameLoader->IsNetworkCreated(); + + MOZ_ASSERT_IF(aState.mTryUseBFCache, aState.mReplaceBrowsingContext); + if (aState.mTryUseBFCache) { + if (bc) { + SessionHistoryEntry* she = + bc->Canonical()->GetActiveSessionHistoryEntry(); + if (she) { + MOZ_LOG( + gSHIPBFCacheLog, LogLevel::Debug, + ("nsFrameLoaderOwner::ChangeRemotenessCommon: store the old " + "page in bfcache")); + Unused << bc->SetIsInBFCache(true); + she->SetFrameLoader(mFrameLoader); + // Session history owns now the frameloader. + mFrameLoader = nullptr; + } + } } - // Preserve the networkCreated status, as nsDocShells created after a - // process swap may shouldn't change their dynamically-created status. - networkCreated = mFrameLoader->IsNetworkCreated(); - mFrameLoader->Destroy(aSwitchingInProgressLoad); - mFrameLoader = nullptr; + if (mFrameLoader) { + if (aContextType == ChangeRemotenessContextType::PRESERVE) { + mFrameLoader->SetWillChangeProcess(); + } + + // Preserve the networkCreated status, as nsDocShells created after a + // process swap may shouldn't change their dynamically-created status. + mFrameLoader->Destroy(aSwitchingInProgressLoad); + mFrameLoader = nullptr; + } } mFrameLoader = nsFrameLoader::Recreate( @@ -154,34 +178,38 @@ void nsFrameLoaderOwner::ChangeRemotenessCommon( } } + ChangeFrameLoaderCommon(owner); +} + +void nsFrameLoaderOwner::ChangeFrameLoaderCommon(Element* aOwner) { // Now that we've got a new FrameLoader, we need to reset our // nsSubDocumentFrame to use the new FrameLoader. - if (nsSubDocumentFrame* ourFrame = do_QueryFrame(owner->GetPrimaryFrame())) { + if (nsSubDocumentFrame* ourFrame = do_QueryFrame(aOwner->GetPrimaryFrame())) { ourFrame->ResetFrameLoader(); } // If the element is focused, or the current mouse over target then // we need to update that state for the new BrowserParent too. if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) { - if (fm->GetFocusedElement() == owner) { - fm->ActivateRemoteFrameIfNeeded(*owner, + if (fm->GetFocusedElement() == aOwner) { + fm->ActivateRemoteFrameIfNeeded(*aOwner, nsFocusManager::GenerateFocusActionId()); } } - if (owner->GetPrimaryFrame()) { + if (aOwner->GetPrimaryFrame()) { EventStateManager* eventManager = - owner->GetPrimaryFrame()->PresContext()->EventStateManager(); - eventManager->RecomputeMouseEnterStateForRemoteFrame(*owner); + aOwner->GetPrimaryFrame()->PresContext()->EventStateManager(); + eventManager->RecomputeMouseEnterStateForRemoteFrame(*aOwner); } - if (owner->IsXULElement()) { + if (aOwner->IsXULElement()) { // Assuming this element is a XULFrameElement, once we've reset our // FrameLoader, fire an event to act like we've recreated ourselves, similar // to what XULFrameElement does after rebinding to the tree. // ChromeOnlyDispatch is turns on to make sure this isn't fired into // content. - (new mozilla::AsyncEventDispatcher(owner, u"XULFrameLoaderCreated"_ns, + (new mozilla::AsyncEventDispatcher(aOwner, u"XULFrameLoaderCreated"_ns, mozilla::CanBubble::eYes, mozilla::ChromeOnlyDispatch::eYes)) ->RunDOMEventWhenSafe(); @@ -283,3 +311,42 @@ void nsFrameLoaderOwner::SubframeCrashed() { /* inProgress */ false, /* isRemote */ false, /* group */ nullptr, frameLoaderInit, IgnoreErrors()); } + +void nsFrameLoaderOwner::ReplaceFrameLoader(nsFrameLoader* aNewFrameLoader) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("nsFrameLoaderOwner::ReplaceFrameLoader: Replace frameloader")); + + mFrameLoader = aNewFrameLoader; + + if (auto* browserParent = mFrameLoader->GetBrowserParent()) { + browserParent->AddWindowListeners(); + browserParent->ResumeProgressEvents(); + } + + RefPtr owner = do_QueryObject(this); + ChangeFrameLoaderCommon(owner); +} + +void nsFrameLoaderOwner::AttachFrameLoader(nsFrameLoader* aFrameLoader) { + mFrameLoaderList.insertBack(aFrameLoader); +} + +void nsFrameLoaderOwner::DeattachFrameLoader(nsFrameLoader* aFrameLoader) { + if (aFrameLoader->isInList()) { + MOZ_ASSERT(mFrameLoaderList.contains(aFrameLoader)); + aFrameLoader->remove(); + } +} + +void nsFrameLoaderOwner::FrameLoaderDestroying(nsFrameLoader* aFrameLoader) { + if (aFrameLoader == mFrameLoader) { + while (!mFrameLoaderList.isEmpty()) { + RefPtr loader = mFrameLoaderList.popFirst(); + if (loader != mFrameLoader) { + loader->Destroy(); + } + } + } else { + DeattachFrameLoader(aFrameLoader); + } +} diff --git a/dom/base/nsFrameLoaderOwner.h b/dom/base/nsFrameLoaderOwner.h index 8dc3320eda78..26dbfd3b0c0e 100644 --- a/dom/base/nsFrameLoaderOwner.h +++ b/dom/base/nsFrameLoaderOwner.h @@ -18,6 +18,7 @@ class BrowsingContext; class BrowsingContextGroup; class BrowserBridgeChild; class ContentParent; +class Element; struct RemotenessOptions; struct RemotenessChangeState; } // namespace dom @@ -79,6 +80,12 @@ class nsFrameLoaderOwner : public nsISupports { void SubframeCrashed(); + void ReplaceFrameLoader(nsFrameLoader* aNewFrameLoader); + + void AttachFrameLoader(nsFrameLoader* aFrameLoader); + void DeattachFrameLoader(nsFrameLoader* aFrameLoader); + void FrameLoaderDestroying(nsFrameLoader* aFrameLoader); + private: bool UseRemoteSubframes(); @@ -102,9 +109,13 @@ class nsFrameLoaderOwner : public nsISupports { std::function& aFrameLoaderInit, mozilla::ErrorResult& aRv); + void ChangeFrameLoaderCommon(mozilla::dom::Element* aOwner); + protected: virtual ~nsFrameLoaderOwner() = default; RefPtr mFrameLoader; + + mozilla::LinkedList mFrameLoaderList; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsFrameLoaderOwner, NS_FRAMELOADEROWNER_IID) diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h index d6623e55497e..011e50444e38 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -710,6 +710,7 @@ class BrowserParent final : public PBrowserParent, // Suspend nsIWebProgressListener events. This is used to block any further // progress events from the old process when process switching away. void SuspendProgressEvents() { mSuspendedProgressEvents = true; } + void ResumeProgressEvents() { mSuspendedProgressEvents = false; } bool CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex, diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 4614888394de..62e481fa7acf 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -28,6 +28,7 @@ #include "mozilla/Components.h" #include "mozilla/HangDetails.h" #include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" #include "mozilla/LookAndFeel.h" #include "mozilla/MemoryTelemetry.h" #include "mozilla/NullPrincipal.h" @@ -108,6 +109,7 @@ #include "mozilla/media/MediaChild.h" #include "mozilla/net/CaptivePortalService.h" #include "mozilla/net/CookieServiceChild.h" +#include "mozilla/net/DocumentChannelChild.h" #include "mozilla/net/HttpChannelChild.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/plugins/PluginInstanceParent.h" @@ -120,7 +122,9 @@ #include "nsFocusManager.h" #include "nsIConsoleService.h" #include "nsIInputStreamChannel.h" +#include "nsILoadGroup.h" #include "nsIOpenWindowInfo.h" +#include "nsISimpleEnumerator.h" #include "nsIStringBundle.h" #include "nsIURIMutator.h" #include "nsQueryObject.h" @@ -290,6 +294,8 @@ # include "mozilla/CodeCoverageHandler.h" #endif +extern mozilla::LazyLogModule gSHIPBFCacheLog; + using namespace mozilla; using namespace mozilla::docshell; using namespace mozilla::dom::ipc; @@ -4242,6 +4248,69 @@ mozilla::ipc::IPCResult ContentChild::RecvDispatchBeforeUnloadToSubtree( return IPC_OK(); } +mozilla::ipc::IPCResult ContentChild::RecvCanSavePresentation( + const MaybeDiscarded& aTopLevelContext, + Maybe aDocumentChannelId, + CanSavePresentationResolver&& aResolver) { + if (aTopLevelContext.IsNullOrDiscarded()) { + aResolver(false); + return IPC_OK(); + } + + bool canSave = true; + // XXXBFCache pass the flags to telemetry. + uint16_t flags = 0; + BrowsingContext* browsingContext = aTopLevelContext.get(); + browsingContext->PreOrderWalk([&](BrowsingContext* aContext) { + nsIDocShell* docShell = aContext->GetDocShell(); + if (docShell) { + Document* doc = docShell->GetDocument(); + if (doc) { + nsIRequest* request = nullptr; + if (aDocumentChannelId.isSome() && aContext->IsTop()) { + nsCOMPtr loadGroup = doc->GetDocumentLoadGroup(); + if (loadGroup) { + nsCOMPtr requests; + loadGroup->GetRequests(getter_AddRefs(requests)); + bool hasMore = false; + if (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { + // If there is any requests, the only one we allow with bfcache + // is the DocumentChannel request. + nsCOMPtr elem; + requests->GetNext(getter_AddRefs(elem)); + nsCOMPtr identChannel = do_QueryInterface(elem); + if (identChannel && + identChannel->ChannelId() == aDocumentChannelId.value()) { + request = identChannel; + } + } + } + } + // Go through also the subdocuments so that flags are collected. + bool canSaveDoc = doc->CanSavePresentation(request, flags, false); + canSave = canSaveDoc && canSave; + + if (MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug)) { + nsAutoCString uri; + if (doc->GetDocumentURI()) { + uri = doc->GetDocumentURI()->GetSpecOrDefault(); + } + + MOZ_LOG( + gSHIPBFCacheLog, LogLevel::Debug, + ("ContentChild::RecvCanSavePresentation can save presentation " + "[%i] for [%s]", + canSaveDoc, uri.get())); + } + } + } + }); + + aResolver(canSave); + + return IPC_OK(); +} + /* static */ void ContentChild::DispatchBeforeUnloadToSubtree( BrowsingContext* aStartingAt, const DispatchBeforeUnloadToSubtreeResolver& aResolver) { diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index af1c7557f56c..2f83a68120ce 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -824,6 +824,11 @@ class ContentChild final : public PContentChild, const MaybeDiscarded& aStartingAt, DispatchBeforeUnloadToSubtreeResolver&& aResolver); + mozilla::ipc::IPCResult RecvCanSavePresentation( + const MaybeDiscarded& aTopLevelContext, + Maybe aDocumentChannelId, + CanSavePresentationResolver&& aResolve); + public: static void DispatchBeforeUnloadToSubtree( BrowsingContext* aStartingAt, diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 62933ecb4ee2..5d8180065b0b 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -9,6 +9,7 @@ include protocol PBrowser; include protocol PCompositorManager; include protocol PContentPermissionRequest; include protocol PCycleCollectWithLogs; +include protocol PDocumentChannel; include protocol PExternalHelperApp; include protocol PHandlerService; include protocol PFileDescriptorSet; @@ -945,6 +946,9 @@ child: async DispatchBeforeUnloadToSubtree(MaybeDiscardedBrowsingContext aStartingAt) returns (PermitUnloadResult result); + async CanSavePresentation(MaybeDiscardedBrowsingContext aTopLevelContext, + uint64_t? aDocumentChannelId) returns (bool success); + // Update the cached list of codec supported in the given process. async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation, MediaCodecsSupported aSupported); diff --git a/netwerk/ipc/DocumentChannelChild.cpp b/netwerk/ipc/DocumentChannelChild.cpp index 4466f75784b1..713a492c7ffb 100644 --- a/netwerk/ipc/DocumentChannelChild.cpp +++ b/netwerk/ipc/DocumentChannelChild.cpp @@ -11,6 +11,7 @@ #include "mozilla/net/HttpBaseChannel.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_fission.h" #include "nsHashPropertyBag.h" #include "nsIHttpChannelInternal.h" #include "nsIObjectLoadingContent.h" @@ -174,7 +175,14 @@ IPCResult DocumentChannelChild::RecvDisconnectChildListeners( // notify them of the failure. If this is a process switch, then we can just // ignore it silently, and trust that the switch will shut down our docshell // and cancel us when it's ready. - if (!aSwitchedProcess) { + // XXXBFCache This should be fixed in some better way. + bool disconnectChildListeners = !aSwitchedProcess; + if (!disconnectChildListeners && StaticPrefs::fission_bfcacheInParent()) { + nsDocShell* shell = GetDocShell(); + disconnectChildListeners = shell && shell->GetBrowsingContext() && + shell->GetBrowsingContext()->IsTop(); + } + if (disconnectChildListeners) { DisconnectChildListeners(aStatus, aLoadGroupStatus); } return IPC_OK(); diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp index 9dee629f9eec..a4baf9c0f24a 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -46,6 +46,7 @@ #include "nsQueryObject.h" #include "nsRedirectHistoryEntry.h" #include "nsSandboxFlags.h" +#include "nsSHistory.h" #include "nsStringStream.h" #include "nsURILoader.h" #include "nsWebNavigationInfo.h" @@ -64,6 +65,8 @@ mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel"); #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) +extern mozilla::LazyLogModule gSHIPBFCacheLog; + using namespace mozilla::dom; namespace mozilla { @@ -1623,6 +1626,18 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch( changeState.mReplaceBrowsingContext = true; } + if (mozilla::SessionHistoryInParent() && + StaticPrefs::fission_bfcacheInParent() && + nsSHistory::GetMaxTotalViewers() > 0 && !parentWindow && + !browsingContext->HadOriginalOpener() && + browsingContext->Group()->Toplevels().Length() == 1 && + !changeState.mRemoteType.IsEmpty() && + browsingContext->GetHasLoadedNonInitialDocument() && + mLoadStateLoadType != LOAD_ERROR_PAGE) { + changeState.mReplaceBrowsingContext = true; + changeState.mTryUseBFCache = true; + } + LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s", currentRemoteType.get(), changeState.mRemoteType.get())); @@ -1644,6 +1659,44 @@ bool DocumentLoadListener::MaybeTriggerProcessSwitch( // If we're doing a document load, we can immediately perform a process // switch. if (mIsDocumentLoad) { + if (changeState.mTryUseBFCache && wgp) { + if (RefPtr browserParent = wgp->GetBrowserParent()) { + nsTArray> + canSavePromises; + browsingContext->Group()->EachParent([&](ContentParent* aParent) { + RefPtr canSave = + aParent->SendCanSavePresentation(browsingContext, + mDocumentChannelId); + canSavePromises.AppendElement(canSave); + }); + + PContentParent::CanSavePresentationPromise::All( + GetCurrentSerialEventTarget(), canSavePromises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}, browsingContext, + changeState](const nsTArray aCanSaves) mutable { + bool canSave = !aCanSaves.Contains(false); + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("DocumentLoadListener::MaybeTriggerProcessSwitch " + "saving presentation=%i", + canSave)); + changeState.mTryUseBFCache = canSave; + self->TriggerProcessSwitch(browsingContext, changeState); + }, + [self = RefPtr{this}, browsingContext, + changeState](ipc::ResponseRejectReason) mutable { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("DocumentLoadListener::MaybeTriggerProcessSwitch " + "error in trying to save presentation")); + changeState.mTryUseBFCache = false; + self->TriggerProcessSwitch(browsingContext, changeState); + }); + return true; + } + } + + changeState.mTryUseBFCache = false; TriggerProcessSwitch(browsingContext, changeState); return true; }