diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/base/nsCCUncollectableMarker.cpp | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | dom/base/nsCCUncollectableMarker.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/dom/base/nsCCUncollectableMarker.cpp b/dom/base/nsCCUncollectableMarker.cpp new file mode 100644 index 0000000000..8c82a5fea4 --- /dev/null +++ b/dom/base/nsCCUncollectableMarker.cpp @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCCUncollectableMarker.h" +#include "nsIObserverService.h" +#include "nsIDocShell.h" +#include "nsServiceManagerUtils.h" +#include "nsIContentViewer.h" +#include "mozilla/dom/Document.h" +#include "InProcessBrowserChildMessageManager.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "nsIWebNavigation.h" +#include "nsISHistory.h" +#include "nsISHEntry.h" +#include "nsIWindowWatcher.h" +#include "mozilla/Services.h" +#include "nsIAppWindow.h" +#include "nsIAppShellService.h" +#include "nsAppShellCID.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsJSEnvironment.h" +#include "nsFrameLoader.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/dom/ChromeMessageBroadcaster.h" +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ParentProcessMessageManager.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/TimeoutManager.h" +#include "xpcpublic.h" +#include "nsObserverService.h" +#include "nsFocusManager.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULRuntime.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static bool sInited = 0; +// The initial value of sGeneration should not be the same as the +// value it is given at xpcom-shutdown, because this will make any GCs +// before we first CC benignly violate the black-gray invariant, due +// to dom::TraceBlackJS(). +uint32_t nsCCUncollectableMarker::sGeneration = 1; +#ifdef MOZ_XUL +# include "nsXULPrototypeCache.h" +#endif + +NS_IMPL_ISUPPORTS(nsCCUncollectableMarker, nsIObserver) + +/* static */ +nsresult nsCCUncollectableMarker::Init() { + if (sInited) { + return NS_OK; + } + + nsCOMPtr<nsIObserver> marker = new nsCCUncollectableMarker; + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) return NS_ERROR_FAILURE; + + nsresult rv; + + // This makes the observer service hold an owning reference to the marker + rv = obs->AddObserver(marker, "xpcom-shutdown", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = obs->AddObserver(marker, "cycle-collector-begin", false); + NS_ENSURE_SUCCESS(rv, rv); + rv = obs->AddObserver(marker, "cycle-collector-forget-skippable", false); + NS_ENSURE_SUCCESS(rv, rv); + + sInited = true; + + return NS_OK; +} + +static void MarkChildMessageManagers(MessageBroadcaster* aMM) { + aMM->MarkForCC(); + + uint32_t browserChildCount = aMM->ChildCount(); + for (uint32_t j = 0; j < browserChildCount; ++j) { + RefPtr<MessageListenerManager> childMM = aMM->GetChildAt(j); + if (!childMM) { + continue; + } + + RefPtr<MessageBroadcaster> strongNonLeafMM = + MessageBroadcaster::From(childMM); + MessageBroadcaster* nonLeafMM = strongNonLeafMM; + + MessageListenerManager* tabMM = childMM; + + strongNonLeafMM = nullptr; + childMM = nullptr; + + if (nonLeafMM) { + MarkChildMessageManagers(nonLeafMM); + continue; + } + + tabMM->MarkForCC(); + + // XXX hack warning, but works, since we know that + // callback is frameloader. + mozilla::dom::ipc::MessageManagerCallback* cb = tabMM->GetCallback(); + if (cb) { + nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb); + InProcessBrowserChildMessageManager* et = + fl->GetBrowserChildMessageManager(); + if (!et) { + continue; + } + et->MarkForCC(); + EventListenerManager* elm = et->GetExistingListenerManager(); + if (elm) { + elm->MarkForCC(); + } + } + } +} + +static void MarkMessageManagers() { + if (nsFrameMessageManager::GetChildProcessManager()) { + // ContentProcessMessageManager's MarkForCC also marks ChildProcessManager. + ContentProcessMessageManager* pg = ContentProcessMessageManager::Get(); + if (pg) { + pg->MarkForCC(); + } + } + + // The global message manager only exists in the root process. + if (!XRE_IsParentProcess()) { + return; + } + RefPtr<ChromeMessageBroadcaster> strongGlobalMM = + nsFrameMessageManager::GetGlobalMessageManager(); + if (!strongGlobalMM) { + return; + } + ChromeMessageBroadcaster* globalMM = strongGlobalMM; + strongGlobalMM = nullptr; + MarkChildMessageManagers(globalMM); + + if (nsFrameMessageManager::sParentProcessManager) { + nsFrameMessageManager::sParentProcessManager->MarkForCC(); + uint32_t childCount = + nsFrameMessageManager::sParentProcessManager->ChildCount(); + for (uint32_t i = 0; i < childCount; ++i) { + RefPtr<MessageListenerManager> childMM = + nsFrameMessageManager::sParentProcessManager->GetChildAt(i); + if (!childMM) { + continue; + } + MessageListenerManager* child = childMM; + childMM = nullptr; + child->MarkForCC(); + } + } + if (nsFrameMessageManager::sSameProcessParentManager) { + nsFrameMessageManager::sSameProcessParentManager->MarkForCC(); + } +} + +void MarkContentViewer(nsIContentViewer* aViewer, bool aCleanupJS) { + if (!aViewer) { + return; + } + + Document* doc = aViewer->GetDocument(); + if (doc && + doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) { + doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); + if (aCleanupJS) { + EventListenerManager* elm = doc->GetExistingListenerManager(); + if (elm) { + elm->MarkForCC(); + } + RefPtr<nsGlobalWindowInner> win = + nsGlobalWindowInner::Cast(doc->GetInnerWindow()); + if (win) { + elm = win->GetExistingListenerManager(); + if (elm) { + elm->MarkForCC(); + } + win->TimeoutManager().UnmarkGrayTimers(); + } + } + } + if (doc) { + if (nsPIDOMWindowInner* inner = doc->GetInnerWindow()) { + inner->MarkUncollectableForCCGeneration( + nsCCUncollectableMarker::sGeneration); + } + if (nsPIDOMWindowOuter* outer = doc->GetWindow()) { + outer->MarkUncollectableForCCGeneration( + nsCCUncollectableMarker::sGeneration); + } + } +} + +void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS); + +void MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS) { + if (!aSHEntry) { + return; + } + + nsCOMPtr<nsIContentViewer> cview; + aSHEntry->GetContentViewer(getter_AddRefs(cview)); + MarkContentViewer(cview, aCleanupJS); + + nsCOMPtr<nsIDocShellTreeItem> child; + int32_t i = 0; + while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + MarkDocShell(child, aCleanupJS); + } + + int32_t count; + aSHEntry->GetChildCount(&count); + for (i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> childEntry; + aSHEntry->GetChildAt(i, getter_AddRefs(childEntry)); + MarkSHEntry(childEntry, aCleanupJS); + } +} + +void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS) { + nsCOMPtr<nsIDocShell> shell = do_QueryInterface(aNode); + if (!shell) { + return; + } + + nsCOMPtr<nsIContentViewer> cview; + shell->GetContentViewer(getter_AddRefs(cview)); + MarkContentViewer(cview, aCleanupJS); + + nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell); + RefPtr<ChildSHistory> history = webNav->GetSessionHistory(); + IgnoredErrorResult ignore; + nsISHistory* legacyHistory = + history ? history->GetLegacySHistory(ignore) : nullptr; + if (legacyHistory) { + MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent()); + int32_t historyCount = history->Count(); + for (int32_t i = 0; i < historyCount; ++i) { + nsCOMPtr<nsISHEntry> shEntry; + legacyHistory->GetEntryAtIndex(i, getter_AddRefs(shEntry)); + + MarkSHEntry(shEntry, aCleanupJS); + } + } + + int32_t i, childCount; + aNode->GetInProcessChildCount(&childCount); + for (i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> child; + aNode->GetInProcessChildAt(i, getter_AddRefs(child)); + MarkDocShell(child, aCleanupJS); + } +} + +void MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS) { + nsCOMPtr<nsISupports> iter; + while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) && iter) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(iter)) { + nsCOMPtr<nsIDocShell> rootDocShell = window->GetDocShell(); + + MarkDocShell(rootDocShell, aCleanupJS); + + RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(rootDocShell); + if (browserChild) { + RefPtr<BrowserChildMessageManager> mm = + browserChild->GetMessageManager(); + if (mm) { + // MarkForCC ends up calling UnmarkGray on message listeners, which + // TraceBlackJS can't do yet. + mm->MarkForCC(); + } + } + } + } +} + +nsresult nsCCUncollectableMarker::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-shutdown")) { + Element::ClearContentUnbinder(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) return NS_ERROR_FAILURE; + + // No need for kungFuDeathGrip here, yay observerservice! + obs->RemoveObserver(this, "xpcom-shutdown"); + obs->RemoveObserver(this, "cycle-collector-begin"); + obs->RemoveObserver(this, "cycle-collector-forget-skippable"); + + sGeneration = 0; + + return NS_OK; + } + + NS_ASSERTION(!strcmp(aTopic, "cycle-collector-begin") || + !strcmp(aTopic, "cycle-collector-forget-skippable"), + "wrong topic"); + + // JS cleanup can be slow. Do it only if this is the first forget-skippable + // after a GC. + const bool cleanupJS = nsJSContext::HasHadCleanupSinceLastGC() && + !strcmp(aTopic, "cycle-collector-forget-skippable"); + + const bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin"); + if (prepareForCC) { + Element::ClearContentUnbinder(); + } + + // Increase generation to effectively unmark all current objects + if (!++sGeneration) { + ++sGeneration; + } + + nsFocusManager::MarkUncollectableForCCGeneration(sGeneration); + + nsresult rv; + + // Iterate all toplevel windows + nsCOMPtr<nsISimpleEnumerator> windowList; + nsCOMPtr<nsIWindowMediator> med = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); + if (med) { + rv = med->GetEnumerator(nullptr, getter_AddRefs(windowList)); + NS_ENSURE_SUCCESS(rv, rv); + + MarkWindowList(windowList, cleanupJS); + } + + nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID); + if (ww) { + rv = ww->GetWindowEnumerator(getter_AddRefs(windowList)); + NS_ENSURE_SUCCESS(rv, rv); + + MarkWindowList(windowList, cleanupJS); + } + + nsCOMPtr<nsIAppShellService> appShell = + do_GetService(NS_APPSHELLSERVICE_CONTRACTID); + if (appShell) { + bool hasHiddenWindow = false; + appShell->GetHasHiddenWindow(&hasHiddenWindow); + if (hasHiddenWindow) { + nsCOMPtr<nsIAppWindow> hw; + appShell->GetHiddenWindow(getter_AddRefs(hw)); + nsCOMPtr<nsIDocShell> shell; + hw->GetDocShell(getter_AddRefs(shell)); + MarkDocShell(shell, cleanupJS); + } + } + +#ifdef MOZ_XUL + nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance(); + if (xulCache) { + xulCache->MarkInCCGeneration(sGeneration); + } +#endif + + enum ForgetSkippableCleanupState { + eInitial = 0, + eUnmarkJSEventListeners = 1, + eUnmarkMessageManagers = 2, + eUnmarkStrongObservers = 3, + eUnmarkJSHolders = 4, + eDone = 5 + }; + + static_assert(eDone == kMajorForgetSkippableCalls, + "There must be one forgetSkippable call per cleanup state."); + + static uint32_t sFSState = eDone; + if (prepareForCC) { + sFSState = eDone; + return NS_OK; + } + + if (cleanupJS) { + // After a GC we start clean up phases from the beginning, + // but we don't want to do the additional clean up phases here + // since we have done already plenty of gray unmarking while going through + // frame message managers and docshells. + sFSState = eInitial; + return NS_OK; + } else { + ++sFSState; + } + + switch (sFSState) { + case eUnmarkJSEventListeners: { + nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments(); + break; + } + case eUnmarkMessageManagers: { + MarkMessageManagers(); + break; + } + case eUnmarkStrongObservers: { + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + static_cast<nsObserverService*>(obs.get())->UnmarkGrayStrongObservers(); + break; + } + case eUnmarkJSHolders: { + xpc_UnmarkSkippableJSHolders(); + break; + } + default: { + break; + } + } + + return NS_OK; +} + +void mozilla::dom::TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC) { +#ifdef MOZ_XUL + // Mark the scripts held in the XULPrototypeCache. This is required to keep + // the JS script in the cache live across GC. + nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance(); + if (cache) { + if (aIsShutdownGC) { + cache->FlushScripts(); + } else { + cache->MarkInGC(aTrc); + } + } +#endif + + if (!nsCCUncollectableMarker::sGeneration) { + return; + } + + if (ContentProcessMessageManager::WasCreated() && + nsFrameMessageManager::GetChildProcessManager()) { + auto* pg = ContentProcessMessageManager::Get(); + if (pg) { + mozilla::TraceScriptHolder(ToSupports(pg), aTrc); + } + } + + // Mark globals of active windows black. + nsGlobalWindowOuter::OuterWindowByIdTable* windowsById = + nsGlobalWindowOuter::GetWindowsTable(); + if (windowsById) { + for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) { + nsGlobalWindowOuter* window = iter.Data(); + if (!window->IsCleanedUp()) { + nsGlobalWindowInner* inner = nullptr; + for (PRCList* win = PR_LIST_HEAD(window); win != window; + win = PR_NEXT_LINK(inner)) { + inner = static_cast<nsGlobalWindowInner*>(win); + if (inner->IsCurrentInnerWindow() || + (inner->GetExtantDoc() && + inner->GetExtantDoc()->GetBFCacheEntry())) { + inner->TraceGlobalJSObject(aTrc); + EventListenerManager* elm = inner->GetExistingListenerManager(); + if (elm) { + elm->TraceListeners(aTrc); + } + CustomElementRegistry* cer = inner->GetExistingCustomElements(); + if (cer) { + cer->TraceDefinitions(aTrc); + } + } + } + + if (window->IsRootOuterWindow()) { + // In child process trace all the BrowserChildMessageManagers. + // Since there is one root outer window per + // BrowserChildMessageManager, we need to look for only those windows, + // not all. + nsIDocShell* ds = window->GetDocShell(); + if (ds) { + nsCOMPtr<nsIBrowserChild> browserChild = ds->GetBrowserChild(); + if (browserChild) { + RefPtr<ContentFrameMessageManager> mm; + browserChild->GetMessageManager(getter_AddRefs(mm)); + if (mm) { + nsCOMPtr<nsISupports> browserChildAsSupports = + do_QueryInterface(browserChild); + mozilla::TraceScriptHolder(browserChildAsSupports, aTrc); + EventListenerManager* elm = mm->GetExistingListenerManager(); + if (elm) { + elm->TraceListeners(aTrc); + } + // As of now there isn't an easy way to trace message listeners. + } + } + } + } + +#ifdef MOZ_XUL + Document* doc = window->GetExtantDoc(); + if (doc) { + doc->TraceProtos(aTrc); + } +#endif + } + } + } +} |