Backed out changeset 1b14354719c0 (bug 1895254) for causing bustages on NavigationTra...
[gecko.git] / docshell / shistory / nsSHEntryShared.cpp
blob2c0e33ef621a6d317ca691586163b7f8b7d154c8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsSHEntryShared.h"
9 #include "nsArray.h"
10 #include "nsContentUtils.h"
11 #include "nsDocShellEditorData.h"
12 #include "nsIDocumentViewer.h"
13 #include "nsISHistory.h"
14 #include "mozilla/dom/Document.h"
15 #include "nsILayoutHistoryState.h"
16 #include "nsIWebNavigation.h"
17 #include "nsSHistory.h"
18 #include "nsThreadUtils.h"
19 #include "nsFrameLoader.h"
20 #include "mozilla/Attributes.h"
21 #include "mozilla/Preferences.h"
23 namespace dom = mozilla::dom;
25 namespace {
26 uint64_t gSHEntrySharedID = 0;
27 nsTHashMap<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>*
28 sIdToSharedState = nullptr;
29 } // namespace
31 namespace mozilla {
32 namespace dom {
34 /* static */
35 uint64_t SHEntrySharedState::GenerateId() {
36 return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID);
39 /* static */
40 SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) {
41 MOZ_ASSERT(aId != 0);
43 return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr;
46 static void AddSHEntrySharedParentState(
47 SHEntrySharedParentState* aSharedState) {
48 MOZ_ASSERT(aSharedState->mId != 0);
50 if (!sIdToSharedState) {
51 sIdToSharedState =
52 new nsTHashMap<nsUint64HashKey, SHEntrySharedParentState*>();
54 sIdToSharedState->InsertOrUpdate(aSharedState->mId, aSharedState);
57 SHEntrySharedParentState::SHEntrySharedParentState() {
58 AddSHEntrySharedParentState(this);
61 SHEntrySharedParentState::SHEntrySharedParentState(
62 nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
63 nsIPrincipal* aPartitionedPrincipalToInherit,
64 nsIContentSecurityPolicy* aCsp, const nsACString& aContentType)
65 : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit,
66 aPartitionedPrincipalToInherit, aCsp, aContentType) {
67 AddSHEntrySharedParentState(this);
70 SHEntrySharedParentState::~SHEntrySharedParentState() {
71 MOZ_ASSERT(mId != 0);
73 RefPtr<nsFrameLoader> loader = mFrameLoader;
74 SetFrameLoader(nullptr);
75 if (loader) {
76 if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction(
77 "SHEntrySharedParentState::~SHEntrySharedParentState",
78 [loader]() -> void { loader->AsyncDestroy(); })))) {
79 // Trigger AsyncDestroy immediately during shutdown.
80 loader->AsyncDestroy();
84 sIdToSharedState->Remove(mId);
85 if (sIdToSharedState->IsEmpty()) {
86 delete sIdToSharedState;
87 sIdToSharedState = nullptr;
91 void SHEntrySharedParentState::ChangeId(uint64_t aId) {
92 MOZ_ASSERT(aId != 0);
94 sIdToSharedState->Remove(mId);
95 mId = aId;
96 sIdToSharedState->InsertOrUpdate(mId, this);
99 void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) {
100 mDocShellID = aEntry->mDocShellID;
101 mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
102 mPrincipalToInherit = aEntry->mPrincipalToInherit;
103 mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit;
104 mCsp = aEntry->mCsp;
105 mSaveLayoutState = aEntry->mSaveLayoutState;
106 mContentType.Assign(aEntry->mContentType);
107 mIsFrameNavigation = aEntry->mIsFrameNavigation;
108 mSticky = aEntry->mSticky;
109 mDynamicallyCreated = aEntry->mDynamicallyCreated;
110 mCacheKey = aEntry->mCacheKey;
111 mLastTouched = aEntry->mLastTouched;
114 void dom::SHEntrySharedParentState::NotifyListenersDocumentViewerEvicted() {
115 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
116 RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get());
117 nsshistory->NotifyListenersDocumentViewerEvicted(1);
121 void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) {
122 mChildShells.AppendObjects(aEntry->mChildShells);
125 void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader* aFrameLoader) {
126 // If expiration tracker is removing this object, IsTracked() returns false.
127 if (GetExpirationState()->IsTracked() && mFrameLoader) {
128 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
129 shistory->RemoveFromExpirationTracker(this);
133 mFrameLoader = aFrameLoader;
135 if (mFrameLoader) {
136 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
137 shistory->AddToExpirationTracker(this);
142 nsFrameLoader* SHEntrySharedParentState::GetFrameLoader() {
143 return mFrameLoader;
146 } // namespace dom
147 } // namespace mozilla
149 void nsSHEntryShared::Shutdown() {}
151 nsSHEntryShared::~nsSHEntryShared() {
152 // The destruction can be caused by either the entry is removed from session
153 // history and no one holds the reference, or the whole session history is on
154 // destruction. We want to ensure that we invoke
155 // shistory->RemoveFromExpirationTracker for the former case.
156 RemoveFromExpirationTracker();
158 // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
159 // there couldn't be any SHEntry holding this shared entry, and we noticed
160 // that calling RemoveDynEntriesForBFCacheEntry in the middle of
161 // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
162 // before RemoveFromBFCacheSync.
163 mSHistory = nullptr;
164 if (mDocumentViewer) {
165 RemoveFromBFCacheSync();
169 NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
170 NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
171 NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
173 already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate() {
174 RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
176 newEntry->dom::SHEntrySharedParentState::CopyFrom(this);
177 newEntry->dom::SHEntrySharedChildState::CopyFrom(this);
179 return newEntry.forget();
182 void nsSHEntryShared::RemoveFromExpirationTracker() {
183 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
184 if (shistory && GetExpirationState()->IsTracked()) {
185 shistory->RemoveFromExpirationTracker(this);
189 void nsSHEntryShared::SyncPresentationState() {
190 if (mDocumentViewer && mWindowState) {
191 // If we have a content viewer and a window state, we should be ok.
192 return;
195 DropPresentationState();
198 void nsSHEntryShared::DropPresentationState() {
199 RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
201 if (mDocument) {
202 mDocument->SetBFCacheEntry(nullptr);
203 mDocument->RemoveMutationObserver(this);
204 mDocument = nullptr;
206 if (mDocumentViewer) {
207 mDocumentViewer->ClearHistoryEntry();
210 RemoveFromExpirationTracker();
211 mDocumentViewer = nullptr;
212 mSticky = true;
213 mWindowState = nullptr;
214 mViewerBounds.SetRect(0, 0, 0, 0);
215 mChildShells.Clear();
216 mRefreshURIList = nullptr;
217 mEditorData = nullptr;
220 nsresult nsSHEntryShared::SetDocumentViewer(nsIDocumentViewer* aViewer) {
221 MOZ_ASSERT(!aViewer || !mDocumentViewer,
222 "SHEntryShared already contains viewer");
224 if (mDocumentViewer || !aViewer) {
225 DropPresentationState();
228 // If we're setting mDocumentViewer to null, state should already be cleared
229 // in the DropPresentationState() call above; If we're setting it to a
230 // non-null content viewer, the entry shouldn't have been tracked either.
231 MOZ_ASSERT(!GetExpirationState()->IsTracked());
232 mDocumentViewer = aViewer;
234 if (mDocumentViewer) {
235 // mSHistory is only set for root entries, but in general bfcache only
236 // applies to root entries as well. BFCache for subframe navigation has been
237 // disabled since 2005 in bug 304860.
238 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
239 shistory->AddToExpirationTracker(this);
242 // Store observed document in strong pointer in case it is removed from
243 // the contentviewer
244 mDocument = mDocumentViewer->GetDocument();
245 if (mDocument) {
246 mDocument->SetBFCacheEntry(this);
247 mDocument->AddMutationObserver(this);
251 return NS_OK;
254 nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
255 MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!");
257 // The call to DropPresentationState could drop the last reference, so hold
258 // |this| until RemoveDynEntriesForBFCacheEntry finishes.
259 RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
261 // DropPresentationState would clear mDocumentViewer.
262 nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
263 DropPresentationState();
265 if (viewer) {
266 viewer->Destroy();
269 // Now that we've dropped the viewer, we have to clear associated dynamic
270 // subframe entries.
271 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
272 if (shistory) {
273 shistory->RemoveDynEntriesForBFCacheEntry(this);
276 return NS_OK;
279 nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
280 MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!");
282 // Check it again to play safe in release builds.
283 if (!mDocument) {
284 return NS_ERROR_UNEXPECTED;
287 // DropPresentationState would clear mDocumentViewer & mDocument. Capture and
288 // release the references asynchronously so that the document doesn't get
289 // nuked mid-mutation.
290 nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
291 RefPtr<dom::Document> document = mDocument;
292 RefPtr<nsSHEntryShared> self = this;
293 nsresult rv = mDocument->Dispatch(NS_NewRunnableFunction(
294 "nsSHEntryShared::RemoveFromBFCacheAsync", [self, viewer, document]() {
295 if (viewer) {
296 viewer->Destroy();
299 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(self->mSHistory);
300 if (shistory) {
301 shistory->RemoveDynEntriesForBFCacheEntry(self);
303 }));
305 if (NS_FAILED(rv)) {
306 NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
307 } else {
308 // Drop presentation. Only do this if we succeeded in posting the event
309 // since otherwise the document could be torn down mid-mutation, causing
310 // crashes.
311 DropPresentationState();
314 return NS_OK;
317 // Don't evict a page from bfcache for attribute mutations on NAC subtrees like
318 // scrollbars.
319 static bool IgnoreMutationForBfCache(const nsINode& aNode) {
320 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) {
321 if (!node->ChromeOnlyAccess()) {
322 break;
324 // Make sure we find a scrollbar in the ancestor chain, to be safe.
325 if (node->IsXULElement(nsGkAtoms::scrollbar)) {
326 return true;
329 return false;
332 void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
333 const CharacterDataChangeInfo&) {
334 if (!IgnoreMutationForBfCache(*aContent)) {
335 RemoveFromBFCacheAsync();
339 void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
340 int32_t aNameSpaceID, nsAtom* aAttribute,
341 int32_t aModType,
342 const nsAttrValue* aOldValue) {
343 if (!IgnoreMutationForBfCache(*aElement)) {
344 RemoveFromBFCacheAsync();
348 void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) {
349 if (!IgnoreMutationForBfCache(*aFirstNewContent)) {
350 RemoveFromBFCacheAsync();
354 void nsSHEntryShared::ContentInserted(nsIContent* aChild) {
355 if (!IgnoreMutationForBfCache(*aChild)) {
356 RemoveFromBFCacheAsync();
360 void nsSHEntryShared::ContentRemoved(nsIContent* aChild,
361 nsIContent* aPreviousSibling) {
362 if (!IgnoreMutationForBfCache(*aChild)) {
363 RemoveFromBFCacheAsync();