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