michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsCCUncollectableMarker.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDocument.h" michael@0: #include "XULDocument.h" michael@0: #include "nsIWindowMediator.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsISHistory.h" michael@0: #include "nsISHEntry.h" michael@0: #include "nsISHContainer.h" michael@0: #include "nsIWindowWatcher.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsIXULWindow.h" michael@0: #include "nsIAppShellService.h" michael@0: #include "nsAppShellCID.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "nsInProcessTabChildGlobal.h" michael@0: #include "nsFrameLoader.h" michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsObserverService.h" michael@0: #include "nsFocusManager.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: static bool sInited = 0; michael@0: uint32_t nsCCUncollectableMarker::sGeneration = 0; michael@0: #ifdef MOZ_XUL michael@0: #include "nsXULPrototypeCache.h" michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCCUncollectableMarker, nsIObserver) michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsCCUncollectableMarker::Init() michael@0: { michael@0: if (sInited) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr marker = new nsCCUncollectableMarker; michael@0: NS_ENSURE_TRUE(marker, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: if (!obs) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv; michael@0: michael@0: // This makes the observer service hold an owning reference to the marker michael@0: rv = obs->AddObserver(marker, "xpcom-shutdown", false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = obs->AddObserver(marker, "cycle-collector-begin", false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = obs->AddObserver(marker, "cycle-collector-forget-skippable", false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: sInited = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: MarkUserData(void* aNode, nsIAtom* aKey, void* aValue, void* aData) michael@0: { michael@0: nsIDocument* d = static_cast(aNode)->GetCurrentDoc(); michael@0: if (d && nsCCUncollectableMarker::InGeneration(d->GetMarkedCCGeneration())) { michael@0: Element::MarkUserData(aNode, aKey, aValue, aData); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: MarkUserDataHandler(void* aNode, nsIAtom* aKey, void* aValue, void* aData) michael@0: { michael@0: nsIDocument* d = static_cast(aNode)->GetCurrentDoc(); michael@0: if (d && nsCCUncollectableMarker::InGeneration(d->GetMarkedCCGeneration())) { michael@0: Element::MarkUserDataHandler(aNode, aKey, aValue, aData); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: MarkMessageManagers() michael@0: { michael@0: // The global message manager only exists in the root process. michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return; michael@0: } michael@0: nsCOMPtr strongGlobalMM = michael@0: do_GetService("@mozilla.org/globalmessagemanager;1"); michael@0: if (!strongGlobalMM) { michael@0: return; michael@0: } michael@0: nsIMessageBroadcaster* globalMM = strongGlobalMM; michael@0: strongGlobalMM = nullptr; michael@0: michael@0: globalMM->MarkForCC(); michael@0: uint32_t childCount = 0; michael@0: globalMM->GetChildCount(&childCount); michael@0: for (uint32_t i = 0; i < childCount; ++i) { michael@0: nsCOMPtr childMM; michael@0: globalMM->GetChildAt(i, getter_AddRefs(childMM)); michael@0: if (!childMM) { michael@0: continue; michael@0: } michael@0: nsCOMPtr strongWindowMM = do_QueryInterface(childMM); michael@0: nsIMessageBroadcaster* windowMM = strongWindowMM; michael@0: childMM = nullptr; michael@0: strongWindowMM = nullptr; michael@0: windowMM->MarkForCC(); michael@0: uint32_t tabChildCount = 0; michael@0: windowMM->GetChildCount(&tabChildCount); michael@0: for (uint32_t j = 0; j < tabChildCount; ++j) { michael@0: nsCOMPtr childMM; michael@0: windowMM->GetChildAt(j, getter_AddRefs(childMM)); michael@0: if (!childMM) { michael@0: continue; michael@0: } michael@0: nsCOMPtr strongTabMM = do_QueryInterface(childMM); michael@0: nsIMessageSender* tabMM = strongTabMM; michael@0: childMM = nullptr; michael@0: strongTabMM = nullptr; michael@0: tabMM->MarkForCC(); michael@0: //XXX hack warning, but works, since we know that michael@0: // callback is frameloader. michael@0: mozilla::dom::ipc::MessageManagerCallback* cb = michael@0: static_cast(tabMM)->GetCallback(); michael@0: if (cb) { michael@0: nsFrameLoader* fl = static_cast(cb); michael@0: EventTarget* et = fl->GetTabChildGlobalAsEventTarget(); michael@0: if (!et) { michael@0: continue; michael@0: } michael@0: static_cast(et)->MarkForCC(); michael@0: EventListenerManager* elm = et->GetExistingListenerManager(); michael@0: if (elm) { michael@0: elm->MarkForCC(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (nsFrameMessageManager::sParentProcessManager) { michael@0: nsFrameMessageManager::sParentProcessManager->MarkForCC(); michael@0: uint32_t childCount = 0; michael@0: nsFrameMessageManager::sParentProcessManager->GetChildCount(&childCount); michael@0: for (uint32_t i = 0; i < childCount; ++i) { michael@0: nsCOMPtr childMM; michael@0: nsFrameMessageManager::sParentProcessManager-> michael@0: GetChildAt(i, getter_AddRefs(childMM)); michael@0: if (!childMM) { michael@0: continue; michael@0: } michael@0: nsIMessageListenerManager* child = childMM; michael@0: childMM = nullptr; michael@0: child->MarkForCC(); michael@0: } michael@0: } michael@0: if (nsFrameMessageManager::sSameProcessParentManager) { michael@0: nsFrameMessageManager::sSameProcessParentManager->MarkForCC(); michael@0: } michael@0: if (nsFrameMessageManager::sChildProcessManager) { michael@0: nsFrameMessageManager::sChildProcessManager->MarkForCC(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MarkContentViewer(nsIContentViewer* aViewer, bool aCleanupJS, michael@0: bool aPrepareForCC) michael@0: { michael@0: if (!aViewer) { michael@0: return; michael@0: } michael@0: michael@0: nsIDocument *doc = aViewer->GetDocument(); michael@0: if (doc && michael@0: doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) { michael@0: doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); michael@0: if (aCleanupJS) { michael@0: EventListenerManager* elm = doc->GetExistingListenerManager(); michael@0: if (elm) { michael@0: elm->MarkForCC(); michael@0: } michael@0: nsCOMPtr win = do_QueryInterface(doc->GetInnerWindow()); michael@0: if (win) { michael@0: elm = win->GetExistingListenerManager(); michael@0: if (elm) { michael@0: elm->MarkForCC(); michael@0: } michael@0: static_cast(win.get())->UnmarkGrayTimers(); michael@0: } michael@0: michael@0: doc->PropertyTable(DOM_USER_DATA_HANDLER)-> michael@0: EnumerateAll(MarkUserDataHandler, &nsCCUncollectableMarker::sGeneration); michael@0: } else if (aPrepareForCC) { michael@0: // Unfortunately we need to still mark user data just before running CC so michael@0: // that it has the right generation. michael@0: doc->PropertyTable(DOM_USER_DATA)-> michael@0: EnumerateAll(MarkUserData, &nsCCUncollectableMarker::sGeneration); michael@0: } michael@0: } michael@0: if (doc) { michael@0: nsPIDOMWindow* inner = doc->GetInnerWindow(); michael@0: if (inner) { michael@0: inner->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); michael@0: } michael@0: nsPIDOMWindow* outer = doc->GetWindow(); michael@0: if (outer) { michael@0: outer->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS, michael@0: bool aPrepareForCC); michael@0: michael@0: void michael@0: MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS, bool aPrepareForCC) michael@0: { michael@0: if (!aSHEntry) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr cview; michael@0: aSHEntry->GetContentViewer(getter_AddRefs(cview)); michael@0: MarkContentViewer(cview, aCleanupJS, aPrepareForCC); michael@0: michael@0: nsCOMPtr child; michael@0: int32_t i = 0; michael@0: while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) && michael@0: child) { michael@0: MarkDocShell(child, aCleanupJS, aPrepareForCC); michael@0: } michael@0: michael@0: nsCOMPtr shCont = do_QueryInterface(aSHEntry); michael@0: int32_t count; michael@0: shCont->GetChildCount(&count); michael@0: for (i = 0; i < count; ++i) { michael@0: nsCOMPtr childEntry; michael@0: shCont->GetChildAt(i, getter_AddRefs(childEntry)); michael@0: MarkSHEntry(childEntry, aCleanupJS, aPrepareForCC); michael@0: } michael@0: michael@0: } michael@0: michael@0: void michael@0: MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS, bool aPrepareForCC) michael@0: { michael@0: nsCOMPtr shell = do_QueryInterface(aNode); michael@0: if (!shell) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr cview; michael@0: shell->GetContentViewer(getter_AddRefs(cview)); michael@0: MarkContentViewer(cview, aCleanupJS, aPrepareForCC); michael@0: michael@0: nsCOMPtr webNav = do_QueryInterface(shell); michael@0: nsCOMPtr history; michael@0: webNav->GetSessionHistory(getter_AddRefs(history)); michael@0: if (history) { michael@0: int32_t i, historyCount; michael@0: history->GetCount(&historyCount); michael@0: for (i = 0; i < historyCount; ++i) { michael@0: nsCOMPtr shEntry; michael@0: history->GetEntryAtIndex(i, false, getter_AddRefs(shEntry)); michael@0: michael@0: MarkSHEntry(shEntry, aCleanupJS, aPrepareForCC); michael@0: } michael@0: } michael@0: michael@0: int32_t i, childCount; michael@0: aNode->GetChildCount(&childCount); michael@0: for (i = 0; i < childCount; ++i) { michael@0: nsCOMPtr child; michael@0: aNode->GetChildAt(i, getter_AddRefs(child)); michael@0: MarkDocShell(child, aCleanupJS, aPrepareForCC); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS, michael@0: bool aPrepareForCC) michael@0: { michael@0: nsCOMPtr iter; michael@0: while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) && michael@0: iter) { michael@0: nsCOMPtr window = do_QueryInterface(iter); michael@0: if (window) { michael@0: nsCOMPtr rootDocShell = window->GetDocShell(); michael@0: michael@0: MarkDocShell(rootDocShell, aCleanupJS, aPrepareForCC); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsCCUncollectableMarker::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: Element::ClearContentUnbinder(); michael@0: michael@0: nsCOMPtr obs = michael@0: mozilla::services::GetObserverService(); michael@0: if (!obs) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // No need for kungFuDeathGrip here, yay observerservice! michael@0: obs->RemoveObserver(this, "xpcom-shutdown"); michael@0: obs->RemoveObserver(this, "cycle-collector-begin"); michael@0: obs->RemoveObserver(this, "cycle-collector-forget-skippable"); michael@0: michael@0: sGeneration = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(!strcmp(aTopic, "cycle-collector-begin") || michael@0: !strcmp(aTopic, "cycle-collector-forget-skippable"), "wrong topic"); michael@0: michael@0: // JS cleanup can be slow. Do it only if there has been a GC. michael@0: bool cleanupJS = michael@0: nsJSContext::CleanupsSinceLastGC() == 0 && michael@0: !strcmp(aTopic, "cycle-collector-forget-skippable"); michael@0: michael@0: bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin"); michael@0: if (prepareForCC) { michael@0: Element::ClearContentUnbinder(); michael@0: } michael@0: michael@0: // Increase generation to effectively unmark all current objects michael@0: if (!++sGeneration) { michael@0: ++sGeneration; michael@0: } michael@0: michael@0: nsFocusManager::MarkUncollectableForCCGeneration(sGeneration); michael@0: michael@0: nsresult rv; michael@0: michael@0: // Iterate all toplevel windows michael@0: nsCOMPtr windowList; michael@0: nsCOMPtr med = michael@0: do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); michael@0: if (med) { michael@0: rv = med->GetEnumerator(nullptr, getter_AddRefs(windowList)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MarkWindowList(windowList, cleanupJS, prepareForCC); michael@0: } michael@0: michael@0: nsCOMPtr ww = michael@0: do_GetService(NS_WINDOWWATCHER_CONTRACTID); michael@0: if (ww) { michael@0: rv = ww->GetWindowEnumerator(getter_AddRefs(windowList)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MarkWindowList(windowList, cleanupJS, prepareForCC); michael@0: } michael@0: michael@0: nsCOMPtr appShell = michael@0: do_GetService(NS_APPSHELLSERVICE_CONTRACTID); michael@0: if (appShell) { michael@0: nsCOMPtr hw; michael@0: appShell->GetHiddenWindow(getter_AddRefs(hw)); michael@0: if (hw) { michael@0: nsCOMPtr shell; michael@0: hw->GetDocShell(getter_AddRefs(shell)); michael@0: MarkDocShell(shell, cleanupJS, prepareForCC); michael@0: } michael@0: bool hasHiddenPrivateWindow = false; michael@0: appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow); michael@0: if (hasHiddenPrivateWindow) { michael@0: appShell->GetHiddenPrivateWindow(getter_AddRefs(hw)); michael@0: if (hw) { michael@0: nsCOMPtr shell; michael@0: hw->GetDocShell(getter_AddRefs(shell)); michael@0: MarkDocShell(shell, cleanupJS, prepareForCC); michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance(); michael@0: if (xulCache) { michael@0: xulCache->MarkInCCGeneration(sGeneration); michael@0: } michael@0: #endif michael@0: michael@0: static bool previousWasJSCleanup = false; michael@0: if (cleanupJS) { michael@0: nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(sGeneration); michael@0: MarkMessageManagers(); michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: static_cast(obs.get())->UnmarkGrayStrongObservers(); michael@0: michael@0: previousWasJSCleanup = true; michael@0: } else if (previousWasJSCleanup) { michael@0: previousWasJSCleanup = false; michael@0: if (!prepareForCC) { michael@0: xpc_UnmarkSkippableJSHolders(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct TraceClosure michael@0: { michael@0: TraceClosure(JSTracer* aTrc, uint32_t aGCNumber) michael@0: : mTrc(aTrc), mGCNumber(aGCNumber) michael@0: {} michael@0: JSTracer* mTrc; michael@0: uint32_t mGCNumber; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: TraceActiveWindowGlobal(const uint64_t& aId, nsGlobalWindow*& aWindow, void* aClosure) michael@0: { michael@0: if (aWindow->GetDocShell() && aWindow->IsOuterWindow()) { michael@0: TraceClosure* closure = static_cast(aClosure); michael@0: aWindow->TraceGlobalJSObject(closure->mTrc); michael@0: #ifdef MOZ_XUL michael@0: nsIDocument* doc = aWindow->GetExtantDoc(); michael@0: if (doc && doc->IsXUL()) { michael@0: XULDocument* xulDoc = static_cast(doc); michael@0: xulDoc->TraceProtos(closure->mTrc, closure->mGCNumber); michael@0: } michael@0: #endif michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: mozilla::dom::TraceBlackJS(JSTracer* aTrc, uint32_t aGCNumber, bool aIsShutdownGC) michael@0: { michael@0: #ifdef MOZ_XUL michael@0: // Mark the scripts held in the XULPrototypeCache. This is required to keep michael@0: // the JS script in the cache live across GC. michael@0: nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance(); michael@0: if (cache) { michael@0: if (aIsShutdownGC) { michael@0: cache->FlushScripts(); michael@0: } else { michael@0: cache->MarkInGC(aTrc); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (!nsCCUncollectableMarker::sGeneration) { michael@0: return; michael@0: } michael@0: michael@0: TraceClosure closure(aTrc, aGCNumber); michael@0: michael@0: // Mark globals of active windows black. michael@0: nsGlobalWindow::WindowByIdTable* windowsById = michael@0: nsGlobalWindow::GetWindowsTable(); michael@0: if (windowsById) { michael@0: windowsById->Enumerate(TraceActiveWindowGlobal, &closure); michael@0: } michael@0: michael@0: // Mark the safe context black michael@0: nsContentUtils::TraceSafeJSContext(aTrc); michael@0: }