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/. */
6
7 #include "nsSHEntryShared.h"
8
9 #include "nsArray.h"
10 #include "nsDocShellEditorData.h"
11 #include "nsIContentViewer.h"
12 #include "nsIDocShell.h"
13 #include "nsIDocShellTreeItem.h"
14 #include "nsIDocument.h"
15 #include "nsIDOMDocument.h"
16 #include "nsILayoutHistoryState.h"
17 #include "nsISHistory.h"
18 #include "nsISHistoryInternal.h"
19 #include "nsIWebNavigation.h"
20 #include "nsThreadUtils.h"
21
22 #include "mozilla/Attributes.h"
23 #include "mozilla/Preferences.h"
24
25 namespace dom = mozilla::dom;
26
27 namespace {
28
29 uint64_t gSHEntrySharedID = 0;
30
31 } // namespace
32
Shutdown()33 void nsSHEntryShared::Shutdown() {}
34
nsSHEntryShared()35 nsSHEntryShared::nsSHEntryShared()
36 : mDocShellID({0}),
37 mLastTouched(0),
38 mID(gSHEntrySharedID++),
39 mViewerBounds(0, 0, 0, 0),
40 mIsFrameNavigation(false),
41 mSaveLayoutState(true),
42 mSticky(true),
43 mDynamicallyCreated(false),
44 mExpired(false) {}
45
~nsSHEntryShared()46 nsSHEntryShared::~nsSHEntryShared() {
47 // The destruction can be caused by either the entry is removed from session
48 // history and no one holds the reference, or the whole session history is on
49 // destruction. We want to ensure that we invoke
50 // shistory->RemoveFromExpirationTracker for the former case.
51 RemoveFromExpirationTracker();
52
53 // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
54 // there couldn't be any SHEntry holding this shared entry, and we noticed
55 // that calling RemoveDynEntriesForBFCacheEntry in the middle of
56 // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
57 // before RemoveFromBFCacheSync.
58 mSHistory = nullptr;
59 if (mContentViewer) {
60 RemoveFromBFCacheSync();
61 }
62 }
63
NS_IMPL_ISUPPORTS(nsSHEntryShared,nsIBFCacheEntry,nsIMutationObserver)64 NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
65
66 already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate(
67 nsSHEntryShared* aEntry) {
68 RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
69
70 newEntry->mDocShellID = aEntry->mDocShellID;
71 newEntry->mChildShells.AppendObjects(aEntry->mChildShells);
72 newEntry->mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
73 newEntry->mPrincipalToInherit = aEntry->mPrincipalToInherit;
74 newEntry->mContentType.Assign(aEntry->mContentType);
75 newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation;
76 newEntry->mSaveLayoutState = aEntry->mSaveLayoutState;
77 newEntry->mSticky = aEntry->mSticky;
78 newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated;
79 newEntry->mCacheKey = aEntry->mCacheKey;
80 newEntry->mLastTouched = aEntry->mLastTouched;
81
82 return newEntry.forget();
83 }
84
RemoveFromExpirationTracker()85 void nsSHEntryShared::RemoveFromExpirationTracker() {
86 nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
87 if (shistory && GetExpirationState()->IsTracked()) {
88 shistory->RemoveFromExpirationTracker(this);
89 }
90 }
91
SyncPresentationState()92 nsresult nsSHEntryShared::SyncPresentationState() {
93 if (mContentViewer && mWindowState) {
94 // If we have a content viewer and a window state, we should be ok.
95 return NS_OK;
96 }
97
98 DropPresentationState();
99
100 return NS_OK;
101 }
102
DropPresentationState()103 void nsSHEntryShared::DropPresentationState() {
104 RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
105
106 if (mDocument) {
107 mDocument->SetBFCacheEntry(nullptr);
108 mDocument->RemoveMutationObserver(this);
109 mDocument = nullptr;
110 }
111 if (mContentViewer) {
112 mContentViewer->ClearHistoryEntry();
113 }
114
115 RemoveFromExpirationTracker();
116 mContentViewer = nullptr;
117 mSticky = true;
118 mWindowState = nullptr;
119 mViewerBounds.SetRect(0, 0, 0, 0);
120 mChildShells.Clear();
121 mRefreshURIList = nullptr;
122 mEditorData = nullptr;
123 }
124
SetContentViewer(nsIContentViewer * aViewer)125 nsresult nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) {
126 NS_PRECONDITION(!aViewer || !mContentViewer,
127 "SHEntryShared already contains viewer");
128
129 if (mContentViewer || !aViewer) {
130 DropPresentationState();
131 }
132
133 // If we're setting mContentViewer to null, state should already be cleared
134 // in the DropPresentationState() call above; If we're setting it to a
135 // non-null content viewer, the entry shouldn't have been tracked either.
136 MOZ_ASSERT(!GetExpirationState()->IsTracked());
137 mContentViewer = aViewer;
138
139 if (mContentViewer) {
140 // mSHistory is only set for root entries, but in general bfcache only
141 // applies to root entries as well. BFCache for subframe navigation has been
142 // disabled since 2005 in bug 304860.
143 if (nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory)) {
144 shistory->AddToExpirationTracker(this);
145 }
146
147 // Store observed document in strong pointer in case it is removed from
148 // the contentviewer
149 mDocument = mContentViewer->GetDocument();
150 if (mDocument) {
151 mDocument->SetBFCacheEntry(this);
152 mDocument->AddMutationObserver(this);
153 }
154 }
155
156 return NS_OK;
157 }
158
RemoveFromBFCacheSync()159 nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
160 MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
161
162 // The call to DropPresentationState could drop the last reference, so hold
163 // |this| until RemoveDynEntriesForBFCacheEntry finishes.
164 RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
165
166 // DropPresentationState would clear mContentViewer.
167 nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
168 DropPresentationState();
169
170 if (viewer) {
171 viewer->Destroy();
172 }
173
174 // Now that we've dropped the viewer, we have to clear associated dynamic
175 // subframe entries.
176 nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
177 if (shistory) {
178 shistory->RemoveDynEntriesForBFCacheEntry(this);
179 }
180
181 return NS_OK;
182 }
183
RemoveFromBFCacheAsync()184 nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
185 MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
186
187 // Check it again to play safe in release builds.
188 if (!mDocument) {
189 return NS_ERROR_UNEXPECTED;
190 }
191
192 // DropPresentationState would clear mContentViewer & mDocument. Capture and
193 // release the references asynchronously so that the document doesn't get
194 // nuked mid-mutation.
195 nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
196 nsCOMPtr<nsIDocument> document = mDocument;
197 RefPtr<nsSHEntryShared> self = this;
198 nsresult rv = mDocument->Dispatch(
199 mozilla::TaskCategory::Other,
200 NS_NewRunnableFunction("nsSHEntryShared::RemoveFromBFCacheAsync",
201 [self, viewer, document]() {
202 if (viewer) {
203 viewer->Destroy();
204 }
205
206 nsCOMPtr<nsISHistoryInternal> shistory =
207 do_QueryReferent(self->mSHistory);
208 if (shistory) {
209 shistory->RemoveDynEntriesForBFCacheEntry(
210 self);
211 }
212 }));
213
214 if (NS_FAILED(rv)) {
215 NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
216 } else {
217 // Drop presentation. Only do this if we succeeded in posting the event
218 // since otherwise the document could be torn down mid-mutation, causing
219 // crashes.
220 DropPresentationState();
221 }
222
223 return NS_OK;
224 }
225
GetID(uint64_t * aID)226 nsresult nsSHEntryShared::GetID(uint64_t* aID) {
227 *aID = mID;
228 return NS_OK;
229 }
230
NodeWillBeDestroyed(const nsINode * aNode)231 void nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode) {
232 NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
233 }
234
CharacterDataWillChange(nsIContent * aContent,const CharacterDataChangeInfo &)235 void nsSHEntryShared::CharacterDataWillChange(nsIContent* aContent,
236 const CharacterDataChangeInfo&) {}
237
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo &)238 void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
239 const CharacterDataChangeInfo&) {
240 RemoveFromBFCacheAsync();
241 }
242
AttributeWillChange(dom::Element * aContent,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aNewValue)243 void nsSHEntryShared::AttributeWillChange(dom::Element* aContent,
244 int32_t aNameSpaceID,
245 nsAtom* aAttribute, int32_t aModType,
246 const nsAttrValue* aNewValue) {}
247
NativeAnonymousChildListChange(nsIContent * aContent,bool aIsRemove)248 void nsSHEntryShared::NativeAnonymousChildListChange(nsIContent* aContent,
249 bool aIsRemove) {}
250
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)251 void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
252 int32_t aNameSpaceID, nsAtom* aAttribute,
253 int32_t aModType,
254 const nsAttrValue* aOldValue) {
255 RemoveFromBFCacheAsync();
256 }
257
ContentAppended(nsIContent * aFirstNewContent)258 void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) {
259 RemoveFromBFCacheAsync();
260 }
261
ContentInserted(nsIContent * aChild)262 void nsSHEntryShared::ContentInserted(nsIContent* aChild) {
263 RemoveFromBFCacheAsync();
264 }
265
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)266 void nsSHEntryShared::ContentRemoved(nsIContent* aChild,
267 nsIContent* aPreviousSibling) {
268 RemoveFromBFCacheAsync();
269 }
270
ParentChainChanged(nsIContent * aContent)271 void nsSHEntryShared::ParentChainChanged(nsIContent* aContent) {}
272