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 "nsIContentViewer.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);
79 sIdToSharedState
->Remove(mId
);
80 if (sIdToSharedState
->IsEmpty()) {
81 delete sIdToSharedState
;
82 sIdToSharedState
= nullptr;
86 void SHEntrySharedParentState::ChangeId(uint64_t aId
) {
89 sIdToSharedState
->Remove(mId
);
91 sIdToSharedState
->InsertOrUpdate(mId
, this);
94 void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState
* aEntry
) {
95 mDocShellID
= aEntry
->mDocShellID
;
96 mTriggeringPrincipal
= aEntry
->mTriggeringPrincipal
;
97 mPrincipalToInherit
= aEntry
->mPrincipalToInherit
;
98 mPartitionedPrincipalToInherit
= aEntry
->mPartitionedPrincipalToInherit
;
100 mSaveLayoutState
= aEntry
->mSaveLayoutState
;
101 mContentType
.Assign(aEntry
->mContentType
);
102 mIsFrameNavigation
= aEntry
->mIsFrameNavigation
;
103 mSticky
= aEntry
->mSticky
;
104 mDynamicallyCreated
= aEntry
->mDynamicallyCreated
;
105 mCacheKey
= aEntry
->mCacheKey
;
106 mLastTouched
= aEntry
->mLastTouched
;
109 void dom::SHEntrySharedParentState::NotifyListenersContentViewerEvicted() {
110 if (nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
)) {
111 RefPtr
<nsSHistory
> nsshistory
= static_cast<nsSHistory
*>(shistory
.get());
112 nsshistory
->NotifyListenersContentViewerEvicted(1);
116 void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState
* aEntry
) {
117 mChildShells
.AppendObjects(aEntry
->mChildShells
);
120 void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader
* aFrameLoader
) {
121 // If expiration tracker is removing this object, IsTracked() returns false.
122 if (GetExpirationState()->IsTracked() && mFrameLoader
) {
123 if (nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
)) {
124 shistory
->RemoveFromExpirationTracker(this);
128 mFrameLoader
= aFrameLoader
;
131 if (nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
)) {
132 shistory
->AddToExpirationTracker(this);
137 nsFrameLoader
* SHEntrySharedParentState::GetFrameLoader() {
142 } // namespace mozilla
144 void nsSHEntryShared::Shutdown() {}
146 nsSHEntryShared::~nsSHEntryShared() {
147 // The destruction can be caused by either the entry is removed from session
148 // history and no one holds the reference, or the whole session history is on
149 // destruction. We want to ensure that we invoke
150 // shistory->RemoveFromExpirationTracker for the former case.
151 RemoveFromExpirationTracker();
153 // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
154 // there couldn't be any SHEntry holding this shared entry, and we noticed
155 // that calling RemoveDynEntriesForBFCacheEntry in the middle of
156 // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
157 // before RemoveFromBFCacheSync.
159 if (mContentViewer
) {
160 RemoveFromBFCacheSync();
164 NS_IMPL_QUERY_INTERFACE(nsSHEntryShared
, nsIBFCacheEntry
, nsIMutationObserver
)
165 NS_IMPL_ADDREF_INHERITED(nsSHEntryShared
, dom::SHEntrySharedParentState
)
166 NS_IMPL_RELEASE_INHERITED(nsSHEntryShared
, dom::SHEntrySharedParentState
)
168 already_AddRefed
<nsSHEntryShared
> nsSHEntryShared::Duplicate() {
169 RefPtr
<nsSHEntryShared
> newEntry
= new nsSHEntryShared();
171 newEntry
->dom::SHEntrySharedParentState::CopyFrom(this);
172 newEntry
->dom::SHEntrySharedChildState::CopyFrom(this);
174 return newEntry
.forget();
177 void nsSHEntryShared::RemoveFromExpirationTracker() {
178 nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
);
179 if (shistory
&& GetExpirationState()->IsTracked()) {
180 shistory
->RemoveFromExpirationTracker(this);
184 void nsSHEntryShared::SyncPresentationState() {
185 if (mContentViewer
&& mWindowState
) {
186 // If we have a content viewer and a window state, we should be ok.
190 DropPresentationState();
193 void nsSHEntryShared::DropPresentationState() {
194 RefPtr
<nsSHEntryShared
> kungFuDeathGrip
= this;
197 mDocument
->SetBFCacheEntry(nullptr);
198 mDocument
->RemoveMutationObserver(this);
201 if (mContentViewer
) {
202 mContentViewer
->ClearHistoryEntry();
205 RemoveFromExpirationTracker();
206 mContentViewer
= nullptr;
208 mWindowState
= nullptr;
209 mViewerBounds
.SetRect(0, 0, 0, 0);
210 mChildShells
.Clear();
211 mRefreshURIList
= nullptr;
212 mEditorData
= nullptr;
215 nsresult
nsSHEntryShared::SetContentViewer(nsIContentViewer
* aViewer
) {
216 MOZ_ASSERT(!aViewer
|| !mContentViewer
,
217 "SHEntryShared already contains viewer");
219 if (mContentViewer
|| !aViewer
) {
220 DropPresentationState();
223 // If we're setting mContentViewer to null, state should already be cleared
224 // in the DropPresentationState() call above; If we're setting it to a
225 // non-null content viewer, the entry shouldn't have been tracked either.
226 MOZ_ASSERT(!GetExpirationState()->IsTracked());
227 mContentViewer
= aViewer
;
229 if (mContentViewer
) {
230 // mSHistory is only set for root entries, but in general bfcache only
231 // applies to root entries as well. BFCache for subframe navigation has been
232 // disabled since 2005 in bug 304860.
233 if (nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
)) {
234 shistory
->AddToExpirationTracker(this);
237 // Store observed document in strong pointer in case it is removed from
239 mDocument
= mContentViewer
->GetDocument();
241 mDocument
->SetBFCacheEntry(this);
242 mDocument
->AddMutationObserver(this);
249 nsresult
nsSHEntryShared::RemoveFromBFCacheSync() {
250 MOZ_ASSERT(mContentViewer
&& mDocument
, "we're not in the bfcache!");
252 // The call to DropPresentationState could drop the last reference, so hold
253 // |this| until RemoveDynEntriesForBFCacheEntry finishes.
254 RefPtr
<nsSHEntryShared
> kungFuDeathGrip
= this;
256 // DropPresentationState would clear mContentViewer.
257 nsCOMPtr
<nsIContentViewer
> viewer
= mContentViewer
;
258 DropPresentationState();
264 // Now that we've dropped the viewer, we have to clear associated dynamic
266 nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(mSHistory
);
268 shistory
->RemoveDynEntriesForBFCacheEntry(this);
274 nsresult
nsSHEntryShared::RemoveFromBFCacheAsync() {
275 MOZ_ASSERT(mContentViewer
&& mDocument
, "we're not in the bfcache!");
277 // Check it again to play safe in release builds.
279 return NS_ERROR_UNEXPECTED
;
282 // DropPresentationState would clear mContentViewer & mDocument. Capture and
283 // release the references asynchronously so that the document doesn't get
284 // nuked mid-mutation.
285 nsCOMPtr
<nsIContentViewer
> viewer
= mContentViewer
;
286 RefPtr
<dom::Document
> document
= mDocument
;
287 RefPtr
<nsSHEntryShared
> self
= this;
288 nsresult rv
= mDocument
->Dispatch(
289 mozilla::TaskCategory::Other
,
290 NS_NewRunnableFunction(
291 "nsSHEntryShared::RemoveFromBFCacheAsync",
292 [self
, viewer
, document
]() {
297 nsCOMPtr
<nsISHistory
> shistory
= do_QueryReferent(self
->mSHistory
);
299 shistory
->RemoveDynEntriesForBFCacheEntry(self
);
304 NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
306 // Drop presentation. Only do this if we succeeded in posting the event
307 // since otherwise the document could be torn down mid-mutation, causing
309 DropPresentationState();
315 void nsSHEntryShared::CharacterDataChanged(nsIContent
* aContent
,
316 const CharacterDataChangeInfo
&) {
317 RemoveFromBFCacheAsync();
320 void nsSHEntryShared::AttributeChanged(dom::Element
* aElement
,
321 int32_t aNameSpaceID
, nsAtom
* aAttribute
,
323 const nsAttrValue
* aOldValue
) {
324 RemoveFromBFCacheAsync();
327 void nsSHEntryShared::ContentAppended(nsIContent
* aFirstNewContent
) {
328 RemoveFromBFCacheAsync();
331 void nsSHEntryShared::ContentInserted(nsIContent
* aChild
) {
332 RemoveFromBFCacheAsync();
335 void nsSHEntryShared::ContentRemoved(nsIContent
* aChild
,
336 nsIContent
* aPreviousSibling
) {
337 RemoveFromBFCacheAsync();