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