nsCCUncollectableMarker.cpp (15531B)
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 "nsCCUncollectableMarker.h" 8 9 #include "InProcessBrowserChildMessageManager.h" 10 #include "mozilla/CycleCollectedJSContext.h" 11 #include "mozilla/CycleCollectedJSRuntime.h" 12 #include "mozilla/EventListenerManager.h" 13 #include "mozilla/Services.h" 14 #include "mozilla/StaticPtr.h" 15 #include "mozilla/dom/BrowserChild.h" 16 #include "mozilla/dom/ChromeMessageBroadcaster.h" 17 #include "mozilla/dom/ContentFrameMessageManager.h" 18 #include "mozilla/dom/ContentProcessMessageManager.h" 19 #include "mozilla/dom/CustomElementRegistry.h" 20 #include "mozilla/dom/Document.h" 21 #include "mozilla/dom/Element.h" 22 #include "mozilla/dom/ParentProcessMessageManager.h" 23 #include "mozilla/dom/TimeoutManager.h" 24 #include "nsAppShellCID.h" 25 #include "nsContentUtils.h" 26 #include "nsFocusManager.h" 27 #include "nsFrameLoader.h" 28 #include "nsGlobalWindowInner.h" 29 #include "nsGlobalWindowOuter.h" 30 #include "nsIAppShellService.h" 31 #include "nsIAppWindow.h" 32 #include "nsIDocShell.h" 33 #include "nsIDocumentViewer.h" 34 #include "nsIInterfaceRequestorUtils.h" 35 #include "nsIObserverService.h" 36 #include "nsISHEntry.h" 37 #include "nsISHistory.h" 38 #include "nsIWebNavigation.h" 39 #include "nsIWindowMediator.h" 40 #include "nsIWindowWatcher.h" 41 #include "nsIXULRuntime.h" 42 #include "nsJSEnvironment.h" 43 #include "nsObserverService.h" 44 #include "nsPIDOMWindow.h" 45 #include "nsServiceManagerUtils.h" 46 #include "xpcpublic.h" 47 48 using namespace mozilla; 49 using namespace mozilla::dom; 50 51 static StaticRefPtr<nsCCUncollectableMarker> sInstance; 52 53 // The initial value of sGeneration should not be the same as the 54 // value it is given at xpcom-shutdown, because this will make any GCs 55 // before we first CC benignly violate the black-gray invariant, due 56 // to dom::TraceBlackJS(). 57 uint32_t nsCCUncollectableMarker::sGeneration = 1; 58 #include "nsXULPrototypeCache.h" 59 60 NS_IMPL_ISUPPORTS(nsCCUncollectableMarker, nsIObserver) 61 62 /* static */ 63 nsresult nsCCUncollectableMarker::Init() { 64 if (sInstance) { 65 return NS_OK; 66 } 67 68 RefPtr<nsCCUncollectableMarker> marker = new nsCCUncollectableMarker(); 69 70 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 71 if (!obs) return NS_ERROR_FAILURE; 72 73 nsresult rv; 74 75 // This makes the observer service hold an owning reference to the marker 76 rv = obs->AddObserver(marker, "xpcom-shutdown", false); 77 NS_ENSURE_SUCCESS(rv, rv); 78 79 rv = obs->AddObserver(marker, "cycle-collector-begin", false); 80 NS_ENSURE_SUCCESS(rv, rv); 81 82 sInstance = marker; 83 84 return NS_OK; 85 } 86 87 static void MarkChildMessageManagers(MessageBroadcaster* aMM) { 88 aMM->MarkForCC(); 89 90 uint32_t browserChildCount = aMM->ChildCount(); 91 for (uint32_t j = 0; j < browserChildCount; ++j) { 92 RefPtr<MessageListenerManager> childMM = aMM->GetChildAt(j); 93 if (!childMM) { 94 continue; 95 } 96 97 RefPtr<MessageBroadcaster> strongNonLeafMM = 98 MessageBroadcaster::From(childMM); 99 MessageBroadcaster* nonLeafMM = strongNonLeafMM; 100 101 MessageListenerManager* tabMM = childMM; 102 103 strongNonLeafMM = nullptr; 104 childMM = nullptr; 105 106 if (nonLeafMM) { 107 MarkChildMessageManagers(nonLeafMM); 108 continue; 109 } 110 111 tabMM->MarkForCC(); 112 113 // XXX hack warning, but works, since we know that 114 // callback is frameloader. 115 mozilla::dom::ipc::MessageManagerCallback* cb = tabMM->GetCallback(); 116 if (cb) { 117 nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb); 118 InProcessBrowserChildMessageManager* et = 119 fl->GetBrowserChildMessageManager(); 120 if (!et) { 121 continue; 122 } 123 et->MarkForCC(); 124 EventListenerManager* elm = et->GetExistingListenerManager(); 125 if (elm) { 126 elm->MarkForCC(); 127 } 128 } 129 } 130 } 131 132 static void MarkMessageManagers() { 133 if (nsFrameMessageManager::GetChildProcessManager()) { 134 // ContentProcessMessageManager's MarkForCC also marks ChildProcessManager. 135 ContentProcessMessageManager* pg = ContentProcessMessageManager::Get(); 136 if (pg) { 137 pg->MarkForCC(); 138 } 139 } 140 141 // The global message manager only exists in the root process. 142 if (!XRE_IsParentProcess()) { 143 return; 144 } 145 RefPtr<ChromeMessageBroadcaster> strongGlobalMM = 146 nsFrameMessageManager::GetGlobalMessageManager(); 147 if (!strongGlobalMM) { 148 return; 149 } 150 ChromeMessageBroadcaster* globalMM = strongGlobalMM; 151 strongGlobalMM = nullptr; 152 MarkChildMessageManagers(globalMM); 153 154 if (nsFrameMessageManager::sParentProcessManager) { 155 nsFrameMessageManager::sParentProcessManager->MarkForCC(); 156 uint32_t childCount = 157 nsFrameMessageManager::sParentProcessManager->ChildCount(); 158 for (uint32_t i = 0; i < childCount; ++i) { 159 RefPtr<MessageListenerManager> childMM = 160 nsFrameMessageManager::sParentProcessManager->GetChildAt(i); 161 if (!childMM) { 162 continue; 163 } 164 MessageListenerManager* child = childMM; 165 childMM = nullptr; 166 child->MarkForCC(); 167 } 168 } 169 if (nsFrameMessageManager::sSameProcessParentManager) { 170 nsFrameMessageManager::sSameProcessParentManager->MarkForCC(); 171 } 172 } 173 174 void MarkDocumentViewer(nsIDocumentViewer* aViewer, bool aCleanupJS) { 175 if (!aViewer) { 176 return; 177 } 178 179 Document* doc = aViewer->GetDocument(); 180 if (doc && 181 doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) { 182 doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); 183 if (aCleanupJS) { 184 EventListenerManager* elm = doc->GetExistingListenerManager(); 185 if (elm) { 186 elm->MarkForCC(); 187 } 188 RefPtr<nsGlobalWindowInner> win = 189 nsGlobalWindowInner::Cast(doc->GetInnerWindow()); 190 if (win) { 191 elm = win->GetExistingListenerManager(); 192 if (elm) { 193 elm->MarkForCC(); 194 } 195 win->GetTimeoutManager()->UnmarkGrayTimers(); 196 } 197 } 198 } 199 if (doc) { 200 if (nsPIDOMWindowInner* inner = doc->GetInnerWindow()) { 201 inner->MarkUncollectableForCCGeneration( 202 nsCCUncollectableMarker::sGeneration); 203 } 204 if (nsPIDOMWindowOuter* outer = doc->GetWindow()) { 205 outer->MarkUncollectableForCCGeneration( 206 nsCCUncollectableMarker::sGeneration); 207 } 208 } 209 } 210 211 void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS); 212 213 void MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS) { 214 if (!aSHEntry) { 215 return; 216 } 217 218 nsCOMPtr<nsIDocumentViewer> viewer; 219 aSHEntry->GetDocumentViewer(getter_AddRefs(viewer)); 220 MarkDocumentViewer(viewer, aCleanupJS); 221 222 nsCOMPtr<nsIDocShellTreeItem> child; 223 int32_t i = 0; 224 while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) && 225 child) { 226 MarkDocShell(child, aCleanupJS); 227 } 228 229 int32_t count; 230 aSHEntry->GetChildCount(&count); 231 for (i = 0; i < count; ++i) { 232 nsCOMPtr<nsISHEntry> childEntry; 233 aSHEntry->GetChildAt(i, getter_AddRefs(childEntry)); 234 MarkSHEntry(childEntry, aCleanupJS); 235 } 236 } 237 238 void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS) { 239 nsCOMPtr<nsIDocShell> shell = do_QueryInterface(aNode); 240 if (!shell) { 241 return; 242 } 243 244 nsCOMPtr<nsIDocumentViewer> viewer; 245 shell->GetDocViewer(getter_AddRefs(viewer)); 246 MarkDocumentViewer(viewer, aCleanupJS); 247 248 nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell); 249 RefPtr<ChildSHistory> history = webNav->GetSessionHistory(); 250 IgnoredErrorResult ignore; 251 nsISHistory* legacyHistory = 252 history ? history->GetLegacySHistory(ignore) : nullptr; 253 if (legacyHistory) { 254 MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent()); 255 int32_t historyCount = history->Count(); 256 for (int32_t i = 0; i < historyCount; ++i) { 257 nsCOMPtr<nsISHEntry> shEntry; 258 legacyHistory->GetEntryAtIndex(i, getter_AddRefs(shEntry)); 259 260 MarkSHEntry(shEntry, aCleanupJS); 261 } 262 } 263 264 int32_t i, childCount; 265 aNode->GetInProcessChildCount(&childCount); 266 for (i = 0; i < childCount; ++i) { 267 nsCOMPtr<nsIDocShellTreeItem> child; 268 aNode->GetInProcessChildAt(i, getter_AddRefs(child)); 269 MarkDocShell(child, aCleanupJS); 270 } 271 } 272 273 void MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS) { 274 nsCOMPtr<nsISupports> iter; 275 while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) && iter) { 276 if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(iter)) { 277 nsCOMPtr<nsIDocShell> rootDocShell = window->GetDocShell(); 278 279 MarkDocShell(rootDocShell, aCleanupJS); 280 281 RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(rootDocShell); 282 if (browserChild) { 283 RefPtr<BrowserChildMessageManager> mm = 284 browserChild->GetMessageManager(); 285 if (mm) { 286 // MarkForCC ends up calling UnmarkGray on message listeners, which 287 // TraceBlackJS can't do yet. 288 mm->MarkForCC(); 289 } 290 } 291 } 292 } 293 } 294 295 nsresult nsCCUncollectableMarker::Observe(nsISupports* aSubject, 296 const char* aTopic, 297 const char16_t* aData) { 298 if (!strcmp(aTopic, "xpcom-shutdown")) { 299 Element::ClearContentUnbinder(); 300 301 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 302 if (!obs) return NS_ERROR_FAILURE; 303 304 // No need for kungFuDeathGrip here, yay observerservice! 305 obs->RemoveObserver(this, "xpcom-shutdown"); 306 obs->RemoveObserver(this, "cycle-collector-begin"); 307 308 sInstance = nullptr; 309 sGeneration = 0; 310 311 return NS_OK; 312 } 313 314 MOZ_ASSERT(!strcmp(aTopic, "cycle-collector-begin"), "wrong topic"); 315 316 Element::ClearContentUnbinder(); 317 return Cleanup(/* aPrepareForCC = */ true); 318 } 319 320 // Don't call this with aPrepareForCC = false from an observer, as apparently 321 // calling UnmarkGrayStrongObservers() from inside an observer can cause 322 // problems. See bug 1958292. 323 nsresult nsCCUncollectableMarker::Cleanup(bool aPrepareForCC) { 324 // JS cleanup can be slow. Do it only if this is the first forget-skippable 325 // after a GC. 326 const bool cleanupJS = 327 nsJSContext::HasHadCleanupSinceLastGC() && !aPrepareForCC; 328 329 // Increase generation to effectively unmark all current objects 330 if (!++sGeneration) { 331 ++sGeneration; 332 } 333 334 nsFocusManager::MarkUncollectableForCCGeneration(sGeneration); 335 336 nsresult rv; 337 338 // Iterate all toplevel windows 339 nsCOMPtr<nsISimpleEnumerator> windowList; 340 nsCOMPtr<nsIWindowMediator> med = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); 341 if (med) { 342 rv = med->GetEnumerator(nullptr, getter_AddRefs(windowList)); 343 NS_ENSURE_SUCCESS(rv, rv); 344 345 MarkWindowList(windowList, cleanupJS); 346 } 347 348 nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID); 349 if (ww) { 350 rv = ww->GetWindowEnumerator(getter_AddRefs(windowList)); 351 NS_ENSURE_SUCCESS(rv, rv); 352 353 MarkWindowList(windowList, cleanupJS); 354 } 355 356 nsCOMPtr<nsIAppShellService> appShell = 357 do_GetService(NS_APPSHELLSERVICE_CONTRACTID); 358 if (appShell) { 359 bool hasHiddenWindow = false; 360 appShell->GetHasHiddenWindow(&hasHiddenWindow); 361 if (hasHiddenWindow) { 362 nsCOMPtr<nsIAppWindow> hw; 363 appShell->GetHiddenWindow(getter_AddRefs(hw)); 364 nsCOMPtr<nsIDocShell> shell; 365 hw->GetDocShell(getter_AddRefs(shell)); 366 MarkDocShell(shell, cleanupJS); 367 } 368 } 369 370 nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance(); 371 if (xulCache) { 372 xulCache->MarkInCCGeneration(sGeneration); 373 } 374 375 enum ForgetSkippableCleanupState { 376 eInitial = 0, 377 eUnmarkJSEventListeners = 1, 378 eUnmarkMessageManagers = 2, 379 eUnmarkStrongObservers = 3, 380 eUnmarkJSHolders = 4, 381 eDone = 5 382 }; 383 384 static_assert(eDone == kMajorForgetSkippableCalls, 385 "There must be one forgetSkippable call per cleanup state."); 386 387 static uint32_t sFSState = eDone; 388 if (aPrepareForCC) { 389 sFSState = eDone; 390 return NS_OK; 391 } 392 393 if (cleanupJS) { 394 // After a GC we start clean up phases from the beginning, 395 // but we don't want to do the additional clean up phases here 396 // since we have done already plenty of gray unmarking while going through 397 // frame message managers and docshells. 398 sFSState = eInitial; 399 return NS_OK; 400 } 401 ++sFSState; 402 403 switch (sFSState) { 404 case eUnmarkJSEventListeners: { 405 nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(); 406 break; 407 } 408 case eUnmarkMessageManagers: { 409 MarkMessageManagers(); 410 break; 411 } 412 case eUnmarkStrongObservers: { 413 nsCOMPtr<nsIObserverService> obs = 414 mozilla::services::GetObserverService(); 415 static_cast<nsObserverService*>(obs.get())->UnmarkGrayStrongObservers(); 416 break; 417 } 418 case eUnmarkJSHolders: { 419 xpc_UnmarkSkippableJSHolders(); 420 break; 421 } 422 default: { 423 break; 424 } 425 } 426 427 return NS_OK; 428 } 429 430 void nsCCUncollectableMarker::CleanupForForgetSkippable() { 431 if (sInstance) { 432 (void)sInstance->Cleanup(/* aPrepareForCC = */ false); 433 } 434 } 435 436 void mozilla::dom::TraceBlackJS(JSTracer* aTrc) { 437 if (!nsCCUncollectableMarker::sGeneration) { 438 return; 439 } 440 441 if (ContentProcessMessageManager::WasCreated() && 442 nsFrameMessageManager::GetChildProcessManager()) { 443 auto* pg = ContentProcessMessageManager::Get(); 444 if (pg) { 445 mozilla::TraceScriptHolder(ToSupports(pg), aTrc); 446 } 447 } 448 449 // Mark globals of active windows black. 450 nsGlobalWindowOuter::OuterWindowByIdTable* windowsById = 451 nsGlobalWindowOuter::GetWindowsTable(); 452 if (windowsById) { 453 for (nsGlobalWindowOuter* window : windowsById->Values()) { 454 if (!window->IsCleanedUp()) { 455 nsGlobalWindowInner* inner = nullptr; 456 for (PRCList* win = PR_LIST_HEAD(window); win != window; 457 win = PR_NEXT_LINK(inner)) { 458 inner = static_cast<nsGlobalWindowInner*>(win); 459 if (inner->IsCurrentInnerWindow() || 460 (inner->GetExtantDoc() && 461 inner->GetExtantDoc()->GetBFCacheEntry())) { 462 inner->TraceGlobalJSObject(aTrc); 463 EventListenerManager* elm = inner->GetExistingListenerManager(); 464 if (elm) { 465 elm->TraceListeners(aTrc); 466 } 467 CustomElementRegistry* cer = inner->GetExistingCustomElements(); 468 if (cer) { 469 cer->TraceDefinitions(aTrc); 470 } 471 } 472 } 473 474 if (window->IsRootOuterWindow()) { 475 // In child process trace all the BrowserChildMessageManagers. 476 // Since there is one root outer window per 477 // BrowserChildMessageManager, we need to look for only those windows, 478 // not all. 479 nsIDocShell* ds = window->GetDocShell(); 480 if (ds) { 481 nsCOMPtr<nsIBrowserChild> browserChild = ds->GetBrowserChild(); 482 if (browserChild) { 483 RefPtr<ContentFrameMessageManager> mm; 484 browserChild->GetMessageManager(getter_AddRefs(mm)); 485 if (mm) { 486 nsCOMPtr<nsISupports> browserChildAsSupports = 487 do_QueryInterface(browserChild); 488 mozilla::TraceScriptHolder(browserChildAsSupports, aTrc); 489 EventListenerManager* elm = mm->GetExistingListenerManager(); 490 if (elm) { 491 elm->TraceListeners(aTrc); 492 } 493 // As of now there isn't an easy way to trace message listeners. 494 } 495 } 496 } 497 } 498 } 499 } 500 } 501 }