|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "nsSHEntryShared.h" |
|
6 |
|
7 #include "nsIDOMDocument.h" |
|
8 #include "nsISHistory.h" |
|
9 #include "nsISHistoryInternal.h" |
|
10 #include "nsIDocument.h" |
|
11 #include "nsIWebNavigation.h" |
|
12 #include "nsIContentViewer.h" |
|
13 #include "nsIDocShell.h" |
|
14 #include "nsIDocShellTreeItem.h" |
|
15 #include "nsDocShellEditorData.h" |
|
16 #include "nsThreadUtils.h" |
|
17 #include "nsILayoutHistoryState.h" |
|
18 #include "mozilla/Attributes.h" |
|
19 #include "nsISupportsArray.h" |
|
20 |
|
21 namespace dom = mozilla::dom; |
|
22 |
|
23 namespace { |
|
24 |
|
25 uint64_t gSHEntrySharedID = 0; |
|
26 |
|
27 } // anonymous namespace |
|
28 |
|
29 // Hardcode this to time out unused content viewers after 30 minutes |
|
30 // XXX jlebar shouldn't this be a pref? |
|
31 #define CONTENT_VIEWER_TIMEOUT_SECONDS (30*60) |
|
32 |
|
33 typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase; |
|
34 class HistoryTracker MOZ_FINAL : public HistoryTrackerBase { |
|
35 public: |
|
36 // Expire cached contentviewers after 20-30 minutes in the cache. |
|
37 HistoryTracker() |
|
38 : HistoryTrackerBase(1000 * CONTENT_VIEWER_TIMEOUT_SECONDS / 2) |
|
39 { |
|
40 } |
|
41 |
|
42 protected: |
|
43 virtual void NotifyExpired(nsSHEntryShared *aObj) { |
|
44 RemoveObject(aObj); |
|
45 aObj->Expire(); |
|
46 } |
|
47 }; |
|
48 |
|
49 static HistoryTracker *gHistoryTracker = nullptr; |
|
50 |
|
51 void |
|
52 nsSHEntryShared::Startup() |
|
53 { |
|
54 gHistoryTracker = new HistoryTracker(); |
|
55 } |
|
56 |
|
57 void |
|
58 nsSHEntryShared::Shutdown() |
|
59 { |
|
60 delete gHistoryTracker; |
|
61 gHistoryTracker = nullptr; |
|
62 } |
|
63 |
|
64 nsSHEntryShared::nsSHEntryShared() |
|
65 : mDocShellID(0) |
|
66 , mIsFrameNavigation(false) |
|
67 , mSaveLayoutState(true) |
|
68 , mSticky(true) |
|
69 , mDynamicallyCreated(false) |
|
70 , mLastTouched(0) |
|
71 , mID(gSHEntrySharedID++) |
|
72 , mExpired(false) |
|
73 , mViewerBounds(0, 0, 0, 0) |
|
74 { |
|
75 } |
|
76 |
|
77 nsSHEntryShared::~nsSHEntryShared() |
|
78 { |
|
79 RemoveFromExpirationTracker(); |
|
80 |
|
81 #ifdef DEBUG |
|
82 // Check that we're not still on track to expire. We shouldn't be, because |
|
83 // we just removed ourselves! |
|
84 nsExpirationTracker<nsSHEntryShared, 3>::Iterator |
|
85 iterator(gHistoryTracker); |
|
86 |
|
87 nsSHEntryShared *elem; |
|
88 while ((elem = iterator.Next()) != nullptr) { |
|
89 NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); |
|
90 } |
|
91 #endif |
|
92 |
|
93 if (mContentViewer) { |
|
94 RemoveFromBFCacheSync(); |
|
95 } |
|
96 } |
|
97 |
|
98 NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) |
|
99 |
|
100 already_AddRefed<nsSHEntryShared> |
|
101 nsSHEntryShared::Duplicate(nsSHEntryShared *aEntry) |
|
102 { |
|
103 nsRefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared(); |
|
104 |
|
105 newEntry->mDocShellID = aEntry->mDocShellID; |
|
106 newEntry->mChildShells.AppendObjects(aEntry->mChildShells); |
|
107 newEntry->mOwner = aEntry->mOwner; |
|
108 newEntry->mContentType.Assign(aEntry->mContentType); |
|
109 newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation; |
|
110 newEntry->mSaveLayoutState = aEntry->mSaveLayoutState; |
|
111 newEntry->mSticky = aEntry->mSticky; |
|
112 newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated; |
|
113 newEntry->mCacheKey = aEntry->mCacheKey; |
|
114 newEntry->mLastTouched = aEntry->mLastTouched; |
|
115 |
|
116 return newEntry.forget(); |
|
117 } |
|
118 |
|
119 void nsSHEntryShared::RemoveFromExpirationTracker() |
|
120 { |
|
121 if (GetExpirationState()->IsTracked()) { |
|
122 gHistoryTracker->RemoveObject(this); |
|
123 } |
|
124 } |
|
125 |
|
126 nsresult |
|
127 nsSHEntryShared::SyncPresentationState() |
|
128 { |
|
129 if (mContentViewer && mWindowState) { |
|
130 // If we have a content viewer and a window state, we should be ok. |
|
131 return NS_OK; |
|
132 } |
|
133 |
|
134 DropPresentationState(); |
|
135 |
|
136 return NS_OK; |
|
137 } |
|
138 |
|
139 void |
|
140 nsSHEntryShared::DropPresentationState() |
|
141 { |
|
142 nsRefPtr<nsSHEntryShared> kungFuDeathGrip = this; |
|
143 |
|
144 if (mDocument) { |
|
145 mDocument->SetBFCacheEntry(nullptr); |
|
146 mDocument->RemoveMutationObserver(this); |
|
147 mDocument = nullptr; |
|
148 } |
|
149 if (mContentViewer) { |
|
150 mContentViewer->ClearHistoryEntry(); |
|
151 } |
|
152 |
|
153 RemoveFromExpirationTracker(); |
|
154 mContentViewer = nullptr; |
|
155 mSticky = true; |
|
156 mWindowState = nullptr; |
|
157 mViewerBounds.SetRect(0, 0, 0, 0); |
|
158 mChildShells.Clear(); |
|
159 mRefreshURIList = nullptr; |
|
160 mEditorData = nullptr; |
|
161 } |
|
162 |
|
163 void |
|
164 nsSHEntryShared::Expire() |
|
165 { |
|
166 // This entry has timed out. If we still have a content viewer, we need to |
|
167 // evict it. |
|
168 if (!mContentViewer) { |
|
169 return; |
|
170 } |
|
171 nsCOMPtr<nsIDocShell> container; |
|
172 mContentViewer->GetContainer(getter_AddRefs(container)); |
|
173 nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container); |
|
174 if (!treeItem) { |
|
175 return; |
|
176 } |
|
177 // We need to find the root DocShell since only that object has an |
|
178 // SHistory and we need the SHistory to evict content viewers |
|
179 nsCOMPtr<nsIDocShellTreeItem> root; |
|
180 treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root)); |
|
181 nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root); |
|
182 nsCOMPtr<nsISHistory> history; |
|
183 webNav->GetSessionHistory(getter_AddRefs(history)); |
|
184 nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history); |
|
185 if (!historyInt) { |
|
186 return; |
|
187 } |
|
188 historyInt->EvictExpiredContentViewerForEntry(this); |
|
189 } |
|
190 |
|
191 nsresult |
|
192 nsSHEntryShared::SetContentViewer(nsIContentViewer *aViewer) |
|
193 { |
|
194 NS_PRECONDITION(!aViewer || !mContentViewer, |
|
195 "SHEntryShared already contains viewer"); |
|
196 |
|
197 if (mContentViewer || !aViewer) { |
|
198 DropPresentationState(); |
|
199 } |
|
200 |
|
201 mContentViewer = aViewer; |
|
202 |
|
203 if (mContentViewer) { |
|
204 gHistoryTracker->AddObject(this); |
|
205 |
|
206 nsCOMPtr<nsIDOMDocument> domDoc; |
|
207 mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); |
|
208 // Store observed document in strong pointer in case it is removed from |
|
209 // the contentviewer |
|
210 mDocument = do_QueryInterface(domDoc); |
|
211 if (mDocument) { |
|
212 mDocument->SetBFCacheEntry(this); |
|
213 mDocument->AddMutationObserver(this); |
|
214 } |
|
215 } |
|
216 |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 nsresult |
|
221 nsSHEntryShared::RemoveFromBFCacheSync() |
|
222 { |
|
223 NS_ASSERTION(mContentViewer && mDocument, |
|
224 "we're not in the bfcache!"); |
|
225 |
|
226 nsCOMPtr<nsIContentViewer> viewer = mContentViewer; |
|
227 DropPresentationState(); |
|
228 |
|
229 // Warning! The call to DropPresentationState could have dropped the last |
|
230 // reference to this object, so don't access members beyond here. |
|
231 |
|
232 if (viewer) { |
|
233 viewer->Destroy(); |
|
234 } |
|
235 |
|
236 return NS_OK; |
|
237 } |
|
238 |
|
239 class DestroyViewerEvent : public nsRunnable |
|
240 { |
|
241 public: |
|
242 DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument) |
|
243 : mViewer(aViewer), |
|
244 mDocument(aDocument) |
|
245 {} |
|
246 |
|
247 NS_IMETHOD Run() |
|
248 { |
|
249 if (mViewer) { |
|
250 mViewer->Destroy(); |
|
251 } |
|
252 return NS_OK; |
|
253 } |
|
254 |
|
255 nsCOMPtr<nsIContentViewer> mViewer; |
|
256 nsCOMPtr<nsIDocument> mDocument; |
|
257 }; |
|
258 |
|
259 nsresult |
|
260 nsSHEntryShared::RemoveFromBFCacheAsync() |
|
261 { |
|
262 NS_ASSERTION(mContentViewer && mDocument, |
|
263 "we're not in the bfcache!"); |
|
264 |
|
265 // Release the reference to the contentviewer asynchronously so that the |
|
266 // document doesn't get nuked mid-mutation. |
|
267 |
|
268 nsCOMPtr<nsIRunnable> evt = |
|
269 new DestroyViewerEvent(mContentViewer, mDocument); |
|
270 nsresult rv = NS_DispatchToCurrentThread(evt); |
|
271 if (NS_FAILED(rv)) { |
|
272 NS_WARNING("failed to dispatch DestroyViewerEvent"); |
|
273 } else { |
|
274 // Drop presentation. Only do this if we succeeded in posting the event |
|
275 // since otherwise the document could be torn down mid-mutation, causing |
|
276 // crashes. |
|
277 DropPresentationState(); |
|
278 } |
|
279 |
|
280 // Careful! The call to DropPresentationState could have dropped the last |
|
281 // reference to this nsSHEntryShared, so don't access members beyond here. |
|
282 |
|
283 return NS_OK; |
|
284 } |
|
285 |
|
286 nsresult |
|
287 nsSHEntryShared::GetID(uint64_t *aID) |
|
288 { |
|
289 *aID = mID; |
|
290 return NS_OK; |
|
291 } |
|
292 |
|
293 //***************************************************************************** |
|
294 // nsSHEntryShared: nsIMutationObserver |
|
295 //***************************************************************************** |
|
296 |
|
297 void |
|
298 nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode) |
|
299 { |
|
300 NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); |
|
301 } |
|
302 |
|
303 void |
|
304 nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument, |
|
305 nsIContent* aContent, |
|
306 CharacterDataChangeInfo* aInfo) |
|
307 { |
|
308 } |
|
309 |
|
310 void |
|
311 nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument, |
|
312 nsIContent* aContent, |
|
313 CharacterDataChangeInfo* aInfo) |
|
314 { |
|
315 RemoveFromBFCacheAsync(); |
|
316 } |
|
317 |
|
318 void |
|
319 nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument, |
|
320 dom::Element* aContent, |
|
321 int32_t aNameSpaceID, |
|
322 nsIAtom* aAttribute, |
|
323 int32_t aModType) |
|
324 { |
|
325 } |
|
326 |
|
327 void |
|
328 nsSHEntryShared::AttributeChanged(nsIDocument* aDocument, |
|
329 dom::Element* aElement, |
|
330 int32_t aNameSpaceID, |
|
331 nsIAtom* aAttribute, |
|
332 int32_t aModType) |
|
333 { |
|
334 RemoveFromBFCacheAsync(); |
|
335 } |
|
336 |
|
337 void |
|
338 nsSHEntryShared::ContentAppended(nsIDocument* aDocument, |
|
339 nsIContent* aContainer, |
|
340 nsIContent* aFirstNewContent, |
|
341 int32_t /* unused */) |
|
342 { |
|
343 RemoveFromBFCacheAsync(); |
|
344 } |
|
345 |
|
346 void |
|
347 nsSHEntryShared::ContentInserted(nsIDocument* aDocument, |
|
348 nsIContent* aContainer, |
|
349 nsIContent* aChild, |
|
350 int32_t /* unused */) |
|
351 { |
|
352 RemoveFromBFCacheAsync(); |
|
353 } |
|
354 |
|
355 void |
|
356 nsSHEntryShared::ContentRemoved(nsIDocument* aDocument, |
|
357 nsIContent* aContainer, |
|
358 nsIContent* aChild, |
|
359 int32_t aIndexInContainer, |
|
360 nsIContent* aPreviousSibling) |
|
361 { |
|
362 RemoveFromBFCacheAsync(); |
|
363 } |
|
364 |
|
365 void |
|
366 nsSHEntryShared::ParentChainChanged(nsIContent *aContent) |
|
367 { |
|
368 } |