nsSHEntryShared.cpp (11817B)
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 "nsContentUtils.h" 11 #include "nsDocShellEditorData.h" 12 #include "nsIDocumentViewer.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/Preferences.h" 21 22 namespace dom = mozilla::dom; 23 24 namespace { 25 uint64_t gSHEntrySharedID = 0; 26 nsTHashMap<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>* 27 sIdToSharedState = nullptr; 28 } // namespace 29 30 namespace mozilla { 31 namespace dom { 32 33 /* static */ 34 uint64_t SHEntrySharedState::GenerateId() { 35 return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID); 36 } 37 38 /* static */ 39 SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) { 40 MOZ_ASSERT(aId != 0); 41 42 return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr; 43 } 44 45 static void AddSHEntrySharedParentState( 46 SHEntrySharedParentState* aSharedState) { 47 MOZ_ASSERT(aSharedState->mId != 0); 48 49 if (!sIdToSharedState) { 50 sIdToSharedState = 51 new nsTHashMap<nsUint64HashKey, SHEntrySharedParentState*>(); 52 } 53 sIdToSharedState->InsertOrUpdate(aSharedState->mId, aSharedState); 54 } 55 56 SHEntrySharedParentState::SHEntrySharedParentState() { 57 AddSHEntrySharedParentState(this); 58 } 59 60 SHEntrySharedParentState::SHEntrySharedParentState( 61 nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit, 62 nsIPrincipal* aPartitionedPrincipalToInherit, 63 nsIPolicyContainer* aPolicyContainer, const nsACString& aContentType) 64 : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit, 65 aPartitionedPrincipalToInherit, aPolicyContainer, 66 aContentType) { 67 AddSHEntrySharedParentState(this); 68 } 69 70 SHEntrySharedParentState::~SHEntrySharedParentState() { 71 MOZ_ASSERT(mId != 0); 72 73 RefPtr<nsFrameLoader> loader = mFrameLoader; 74 SetFrameLoader(nullptr); 75 if (loader) { 76 if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction( 77 "SHEntrySharedParentState::~SHEntrySharedParentState", 78 [loader]() -> void { loader->AsyncDestroy(); })))) { 79 // Trigger AsyncDestroy immediately during shutdown. 80 loader->AsyncDestroy(); 81 } 82 } 83 84 sIdToSharedState->Remove(mId); 85 if (sIdToSharedState->IsEmpty()) { 86 delete sIdToSharedState; 87 sIdToSharedState = nullptr; 88 } 89 } 90 91 void SHEntrySharedParentState::ChangeId(uint64_t aId) { 92 MOZ_ASSERT(aId != 0); 93 94 sIdToSharedState->Remove(mId); 95 mId = aId; 96 sIdToSharedState->InsertOrUpdate(mId, this); 97 } 98 99 void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) { 100 mDocShellID = aEntry->mDocShellID; 101 mTriggeringPrincipal = aEntry->mTriggeringPrincipal; 102 mPrincipalToInherit = aEntry->mPrincipalToInherit; 103 mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit; 104 mPolicyContainer = aEntry->mPolicyContainer; 105 mSaveLayoutState = aEntry->mSaveLayoutState; 106 mContentType.Assign(aEntry->mContentType); 107 mIsFrameNavigation = aEntry->mIsFrameNavigation; 108 mSticky = aEntry->mSticky; 109 mDynamicallyCreated = aEntry->mDynamicallyCreated; 110 mCacheKey = aEntry->mCacheKey; 111 mLastTouched = aEntry->mLastTouched; 112 } 113 114 void dom::SHEntrySharedParentState::NotifyListenersDocumentViewerEvicted() { 115 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) { 116 RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get()); 117 nsshistory->NotifyListenersDocumentViewerEvicted(1); 118 } 119 } 120 121 void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) { 122 mChildShells.AppendObjects(aEntry->mChildShells); 123 } 124 125 void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader* aFrameLoader) { 126 // If expiration tracker is removing this object, IsTracked() returns false. 127 if (GetExpirationState()->IsTracked() && mFrameLoader) { 128 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) { 129 shistory->RemoveFromExpirationTracker(this); 130 } 131 } 132 133 mFrameLoader = aFrameLoader; 134 135 if (mFrameLoader) { 136 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) { 137 shistory->AddToExpirationTracker(this); 138 } 139 } 140 } 141 142 nsFrameLoader* SHEntrySharedParentState::GetFrameLoader() { 143 return mFrameLoader; 144 } 145 146 } // namespace dom 147 } // namespace mozilla 148 149 void nsSHEntryShared::Shutdown() {} 150 151 nsSHEntryShared::~nsSHEntryShared() { 152 // The destruction can be caused by either the entry is removed from session 153 // history and no one holds the reference, or the whole session history is on 154 // destruction. We want to ensure that we invoke 155 // shistory->RemoveFromExpirationTracker for the former case. 156 RemoveFromExpirationTracker(); 157 158 // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since 159 // there couldn't be any SHEntry holding this shared entry, and we noticed 160 // that calling RemoveDynEntriesForBFCacheEntry in the middle of 161 // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly 162 // before RemoveFromBFCacheSync. 163 mSHistory = nullptr; 164 if (mDocumentViewer) { 165 RemoveFromBFCacheSync(); 166 } 167 } 168 169 NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) 170 NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState) 171 NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState) 172 173 already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate() { 174 RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared(); 175 176 newEntry->dom::SHEntrySharedParentState::CopyFrom(this); 177 newEntry->dom::SHEntrySharedChildState::CopyFrom(this); 178 179 return newEntry.forget(); 180 } 181 182 void nsSHEntryShared::RemoveFromExpirationTracker() { 183 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory); 184 if (shistory && GetExpirationState()->IsTracked()) { 185 shistory->RemoveFromExpirationTracker(this); 186 } 187 } 188 189 void nsSHEntryShared::SyncPresentationState() { 190 if (mDocumentViewer && mWindowState) { 191 // If we have a content viewer and a window state, we should be ok. 192 return; 193 } 194 195 DropPresentationState(); 196 } 197 198 void nsSHEntryShared::DropPresentationState() { 199 RefPtr<nsSHEntryShared> kungFuDeathGrip = this; 200 201 if (mDocument) { 202 mDocument->SetBFCacheEntry(nullptr); 203 mDocument->RemoveMutationObserver(this); 204 mDocument = nullptr; 205 } 206 if (mDocumentViewer) { 207 mDocumentViewer->ClearHistoryEntry(); 208 } 209 210 RemoveFromExpirationTracker(); 211 mDocumentViewer = nullptr; 212 mSticky = true; 213 mWindowState = nullptr; 214 mViewerBounds.SetRect(0, 0, 0, 0); 215 mChildShells.Clear(); 216 mRefreshURIList = nullptr; 217 mEditorData = nullptr; 218 } 219 220 nsresult nsSHEntryShared::SetDocumentViewer(nsIDocumentViewer* aViewer) { 221 MOZ_ASSERT(!aViewer || !mDocumentViewer, 222 "SHEntryShared already contains viewer"); 223 224 if (mDocumentViewer || !aViewer) { 225 DropPresentationState(); 226 } 227 228 // If we're setting mDocumentViewer to null, state should already be cleared 229 // in the DropPresentationState() call above; If we're setting it to a 230 // non-null content viewer, the entry shouldn't have been tracked either. 231 MOZ_ASSERT(!GetExpirationState()->IsTracked()); 232 mDocumentViewer = aViewer; 233 234 if (mDocumentViewer) { 235 // mSHistory is only set for root entries, but in general bfcache only 236 // applies to root entries as well. BFCache for subframe navigation has been 237 // disabled since 2005 in bug 304860. 238 if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) { 239 shistory->AddToExpirationTracker(this); 240 } 241 242 // Store observed document in strong pointer in case it is removed from 243 // the documentviewer 244 mDocument = mDocumentViewer->GetDocument(); 245 if (mDocument) { 246 mDocument->SetBFCacheEntry(this); 247 mDocument->AddMutationObserver(this); 248 } 249 } 250 251 return NS_OK; 252 } 253 254 nsresult nsSHEntryShared::RemoveFromBFCacheSync() { 255 MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!"); 256 257 // The call to DropPresentationState could drop the last reference, so hold 258 // |this| until RemoveDynEntriesForBFCacheEntry finishes. 259 RefPtr<nsSHEntryShared> kungFuDeathGrip = this; 260 261 // DropPresentationState would clear mDocumentViewer. 262 nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer; 263 DropPresentationState(); 264 265 if (viewer) { 266 viewer->Destroy(); 267 } 268 269 // Now that we've dropped the viewer, we have to clear associated dynamic 270 // subframe entries. 271 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory); 272 if (shistory) { 273 shistory->RemoveDynEntriesForBFCacheEntry(this); 274 } 275 276 return NS_OK; 277 } 278 279 nsresult nsSHEntryShared::RemoveFromBFCacheAsync() { 280 MOZ_ASSERT(mDocumentViewer && mDocument, "we're not in the bfcache!"); 281 282 // Check it again to play safe in release builds. 283 if (!mDocument) { 284 return NS_ERROR_UNEXPECTED; 285 } 286 287 // DropPresentationState would clear mDocumentViewer & mDocument. Capture and 288 // release the references asynchronously so that the document doesn't get 289 // nuked mid-mutation. 290 nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer; 291 RefPtr<dom::Document> document = mDocument; 292 RefPtr<nsSHEntryShared> self = this; 293 nsresult rv = mDocument->Dispatch(NS_NewRunnableFunction( 294 "nsSHEntryShared::RemoveFromBFCacheAsync", [self, viewer, document]() { 295 if (viewer) { 296 viewer->Destroy(); 297 } 298 299 nsCOMPtr<nsISHistory> shistory = do_QueryReferent(self->mSHistory); 300 if (shistory) { 301 shistory->RemoveDynEntriesForBFCacheEntry(self); 302 } 303 })); 304 305 if (NS_FAILED(rv)) { 306 NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable."); 307 } else { 308 // Drop presentation. Only do this if we succeeded in posting the event 309 // since otherwise the document could be torn down mid-mutation, causing 310 // crashes. 311 DropPresentationState(); 312 } 313 314 return NS_OK; 315 } 316 317 // Don't evict a page from bfcache for attribute mutations on NAC subtrees like 318 // scrollbars. 319 static bool IgnoreMutationForBfCache(const nsINode& aNode) { 320 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) { 321 if (!node->ChromeOnlyAccess()) { 322 break; 323 } 324 // Make sure we find a scrollbar in the ancestor chain, to be safe. 325 if (node->IsXULElement(nsGkAtoms::scrollbar)) { 326 return true; 327 } 328 } 329 return false; 330 } 331 332 void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent, 333 const CharacterDataChangeInfo&) { 334 if (!IgnoreMutationForBfCache(*aContent)) { 335 RemoveFromBFCacheAsync(); 336 } 337 } 338 339 void nsSHEntryShared::AttributeChanged(dom::Element* aElement, 340 int32_t aNameSpaceID, nsAtom* aAttribute, 341 AttrModType, 342 const nsAttrValue* aOldValue) { 343 if (!IgnoreMutationForBfCache(*aElement)) { 344 RemoveFromBFCacheAsync(); 345 } 346 } 347 348 void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent, 349 const ContentAppendInfo&) { 350 if (!IgnoreMutationForBfCache(*aFirstNewContent)) { 351 RemoveFromBFCacheAsync(); 352 } 353 } 354 355 void nsSHEntryShared::ContentInserted(nsIContent* aChild, 356 const ContentInsertInfo&) { 357 if (!IgnoreMutationForBfCache(*aChild)) { 358 RemoveFromBFCacheAsync(); 359 } 360 } 361 362 void nsSHEntryShared::ContentWillBeRemoved(nsIContent* aChild, 363 const ContentRemoveInfo&) { 364 if (!IgnoreMutationForBfCache(*aChild)) { 365 RemoveFromBFCacheAsync(); 366 } 367 }