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"
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
;
26 uint64_t gSHEntrySharedID
= 0;
27 nsTHashMap
<nsUint64HashKey
, mozilla::dom::SHEntrySharedParentState
*>*
28 sIdToSharedState
= nullptr;
35 uint64_t SHEntrySharedState::GenerateId() {
36 return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID
);
40 SHEntrySharedParentState
* SHEntrySharedParentState::Lookup(uint64_t aId
) {
43 return sIdToSharedState
? sIdToSharedState
->Get(aId
) : nullptr;
46 static void AddSHEntrySharedParentState(
47 SHEntrySharedParentState
* aSharedState
) {
48 MOZ_ASSERT(aSharedState
->mId
!= 0);
50 if (!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() {
73 RefPtr
<nsFrameLoader
> loader
= mFrameLoader
;
74 SetFrameLoader(nullptr);
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
) {
94 sIdToSharedState
->Remove(mId
);
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
;
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
;
136 if (nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
)) {
137 shistory
->AddToExpirationTracker(this);
142 nsFrameLoader
* SHEntrySharedParentState::GetFrameLoader() {
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.
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.
195 DropPresentationState();
198 void nsSHEntryShared::DropPresentationState() {
199 RefPtr
<nsSHEntryShared
> kungFuDeathGrip
= this;
202 mDocument
->SetBFCacheEntry(nullptr);
203 mDocument
->RemoveMutationObserver(this);
206 if (mDocumentViewer
) {
207 mDocumentViewer
->ClearHistoryEntry();
210 RemoveFromExpirationTracker();
211 mDocumentViewer
= nullptr;
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
244 mDocument
= mDocumentViewer
->GetDocument();
246 mDocument
->SetBFCacheEntry(this);
247 mDocument
->AddMutationObserver(this);
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();
269 // Now that we've dropped the viewer, we have to clear associated dynamic
271 nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
);
273 shistory
->RemoveDynEntriesForBFCacheEntry(this);
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.
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
]() {
299 nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(self
->mSHistory
);
301 shistory
->RemoveDynEntriesForBFCacheEntry(self
);
306 NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
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
311 DropPresentationState();
317 // Don't evict a page from bfcache for attribute mutations on NAC subtrees like
319 static bool IgnoreMutationForBfCache(const nsINode
& aNode
) {
320 for (const nsINode
* node
= &aNode
; node
; node
= node
->GetParentNode()) {
321 if (!node
->ChromeOnlyAccess()) {
324 // Make sure we find a scrollbar in the ancestor chain, to be safe.
325 if (node
->IsXULElement(nsGkAtoms::scrollbar
)) {
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
,
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();