Line data Source code
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 : #include "nsIObserverService.h"
9 : #include "nsIDocShell.h"
10 : #include "nsServiceManagerUtils.h"
11 : #include "nsIContentViewer.h"
12 : #include "nsIDocument.h"
13 : #include "XULDocument.h"
14 : #include "nsIWindowMediator.h"
15 : #include "nsPIDOMWindow.h"
16 : #include "nsIWebNavigation.h"
17 : #include "nsISHistory.h"
18 : #include "nsISHEntry.h"
19 : #include "nsISHContainer.h"
20 : #include "nsITabChild.h"
21 : #include "nsIWindowWatcher.h"
22 : #include "mozilla/Services.h"
23 : #include "nsIXULWindow.h"
24 : #include "nsIAppShellService.h"
25 : #include "nsAppShellCID.h"
26 : #include "nsContentUtils.h"
27 : #include "nsGlobalWindow.h"
28 : #include "nsJSEnvironment.h"
29 : #include "nsInProcessTabChildGlobal.h"
30 : #include "nsFrameLoader.h"
31 : #include "mozilla/CycleCollectedJSContext.h"
32 : #include "mozilla/CycleCollectedJSRuntime.h"
33 : #include "mozilla/EventListenerManager.h"
34 : #include "mozilla/dom/Element.h"
35 : #include "mozilla/dom/ProcessGlobal.h"
36 : #include "mozilla/dom/TimeoutManager.h"
37 : #include "xpcpublic.h"
38 : #include "nsObserverService.h"
39 : #include "nsFocusManager.h"
40 : #include "nsIInterfaceRequestorUtils.h"
41 :
42 : using namespace mozilla;
43 : using namespace mozilla::dom;
44 :
45 : static bool sInited = 0;
46 : // The initial value of sGeneration should not be the same as the
47 : // value it is given at xpcom-shutdown, because this will make any GCs
48 : // before we first CC benignly violate the black-gray invariant, due
49 : // to dom::TraceBlackJS().
50 : uint32_t nsCCUncollectableMarker::sGeneration = 1;
51 : #ifdef MOZ_XUL
52 : #include "nsXULPrototypeCache.h"
53 : #endif
54 :
55 24 : NS_IMPL_ISUPPORTS(nsCCUncollectableMarker, nsIObserver)
56 :
57 : /* static */
58 : nsresult
59 3 : nsCCUncollectableMarker::Init()
60 : {
61 3 : if (sInited) {
62 0 : return NS_OK;
63 : }
64 :
65 6 : nsCOMPtr<nsIObserver> marker = new nsCCUncollectableMarker;
66 :
67 : nsCOMPtr<nsIObserverService> obs =
68 6 : mozilla::services::GetObserverService();
69 3 : if (!obs)
70 0 : return NS_ERROR_FAILURE;
71 :
72 : nsresult rv;
73 :
74 : // This makes the observer service hold an owning reference to the marker
75 3 : rv = obs->AddObserver(marker, "xpcom-shutdown", false);
76 3 : NS_ENSURE_SUCCESS(rv, rv);
77 :
78 3 : rv = obs->AddObserver(marker, "cycle-collector-begin", false);
79 3 : NS_ENSURE_SUCCESS(rv, rv);
80 3 : rv = obs->AddObserver(marker, "cycle-collector-forget-skippable", false);
81 3 : NS_ENSURE_SUCCESS(rv, rv);
82 :
83 3 : sInited = true;
84 :
85 3 : return NS_OK;
86 : }
87 :
88 : static void
89 0 : MarkUserData(void* aNode, nsIAtom* aKey, void* aValue, void* aData)
90 : {
91 0 : nsIDocument* d = static_cast<nsINode*>(aNode)->GetUncomposedDoc();
92 0 : if (d && nsCCUncollectableMarker::InGeneration(d->GetMarkedCCGeneration())) {
93 0 : Element::MarkUserData(aNode, aKey, aValue, aData);
94 : }
95 0 : }
96 :
97 : static void
98 0 : MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
99 : {
100 0 : aMM->MarkForCC();
101 :
102 0 : uint32_t tabChildCount = 0;
103 0 : aMM->GetChildCount(&tabChildCount);
104 0 : for (uint32_t j = 0; j < tabChildCount; ++j) {
105 0 : nsCOMPtr<nsIMessageListenerManager> childMM;
106 0 : aMM->GetChildAt(j, getter_AddRefs(childMM));
107 0 : if (!childMM) {
108 0 : continue;
109 : }
110 :
111 0 : nsCOMPtr<nsIMessageBroadcaster> strongNonLeafMM = do_QueryInterface(childMM);
112 0 : nsIMessageBroadcaster* nonLeafMM = strongNonLeafMM;
113 :
114 0 : nsCOMPtr<nsIMessageSender> strongTabMM = do_QueryInterface(childMM);
115 0 : nsIMessageSender* tabMM = strongTabMM;
116 :
117 0 : strongNonLeafMM = nullptr;
118 0 : strongTabMM = nullptr;
119 0 : childMM = nullptr;
120 :
121 0 : if (nonLeafMM) {
122 0 : MarkChildMessageManagers(nonLeafMM);
123 0 : continue;
124 : }
125 :
126 0 : tabMM->MarkForCC();
127 :
128 : //XXX hack warning, but works, since we know that
129 : // callback is frameloader.
130 : mozilla::dom::ipc::MessageManagerCallback* cb =
131 0 : static_cast<nsFrameMessageManager*>(tabMM)->GetCallback();
132 0 : if (cb) {
133 0 : nsFrameLoader* fl = static_cast<nsFrameLoader*>(cb);
134 0 : EventTarget* et = fl->GetTabChildGlobalAsEventTarget();
135 0 : if (!et) {
136 0 : continue;
137 : }
138 0 : static_cast<nsInProcessTabChildGlobal*>(et)->MarkForCC();
139 0 : EventListenerManager* elm = et->GetExistingListenerManager();
140 0 : if (elm) {
141 0 : elm->MarkForCC();
142 : }
143 : }
144 : }
145 0 : }
146 :
147 : static void
148 0 : MarkMessageManagers()
149 : {
150 0 : if (nsFrameMessageManager::GetChildProcessManager()) {
151 : // ProcessGlobal's MarkForCC marks also ChildProcessManager.
152 0 : ProcessGlobal* pg = ProcessGlobal::Get();
153 0 : if (pg) {
154 0 : pg->MarkForCC();
155 : }
156 : }
157 :
158 : // The global message manager only exists in the root process.
159 0 : if (!XRE_IsParentProcess()) {
160 0 : return;
161 : }
162 : nsCOMPtr<nsIMessageBroadcaster> strongGlobalMM =
163 0 : do_GetService("@mozilla.org/globalmessagemanager;1");
164 0 : if (!strongGlobalMM) {
165 0 : return;
166 : }
167 0 : nsIMessageBroadcaster* globalMM = strongGlobalMM;
168 0 : strongGlobalMM = nullptr;
169 0 : MarkChildMessageManagers(globalMM);
170 :
171 0 : if (nsFrameMessageManager::sParentProcessManager) {
172 0 : nsFrameMessageManager::sParentProcessManager->MarkForCC();
173 0 : uint32_t childCount = 0;
174 0 : nsFrameMessageManager::sParentProcessManager->GetChildCount(&childCount);
175 0 : for (uint32_t i = 0; i < childCount; ++i) {
176 0 : nsCOMPtr<nsIMessageListenerManager> childMM;
177 : nsFrameMessageManager::sParentProcessManager->
178 0 : GetChildAt(i, getter_AddRefs(childMM));
179 0 : if (!childMM) {
180 0 : continue;
181 : }
182 0 : nsIMessageListenerManager* child = childMM;
183 0 : childMM = nullptr;
184 0 : child->MarkForCC();
185 : }
186 : }
187 0 : if (nsFrameMessageManager::sSameProcessParentManager) {
188 0 : nsFrameMessageManager::sSameProcessParentManager->MarkForCC();
189 : }
190 : }
191 :
192 : void
193 0 : MarkContentViewer(nsIContentViewer* aViewer, bool aCleanupJS,
194 : bool aPrepareForCC)
195 : {
196 0 : if (!aViewer) {
197 0 : return;
198 : }
199 :
200 0 : nsIDocument *doc = aViewer->GetDocument();
201 0 : if (doc &&
202 0 : doc->GetMarkedCCGeneration() != nsCCUncollectableMarker::sGeneration) {
203 0 : doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
204 0 : if (aCleanupJS) {
205 0 : EventListenerManager* elm = doc->GetExistingListenerManager();
206 0 : if (elm) {
207 0 : elm->MarkForCC();
208 : }
209 0 : nsCOMPtr<EventTarget> win = do_QueryInterface(doc->GetInnerWindow());
210 0 : if (win) {
211 0 : elm = win->GetExistingListenerManager();
212 0 : if (elm) {
213 0 : elm->MarkForCC();
214 : }
215 0 : static_cast<nsGlobalWindow*>(win.get())->AsInner()->
216 0 : TimeoutManager().UnmarkGrayTimers();
217 : }
218 0 : } else if (aPrepareForCC) {
219 : // Unfortunately we need to still mark user data just before running CC so
220 : // that it has the right generation.
221 : doc->PropertyTable(DOM_USER_DATA)->
222 0 : EnumerateAll(MarkUserData, &nsCCUncollectableMarker::sGeneration);
223 : }
224 : }
225 0 : if (doc) {
226 0 : if (nsPIDOMWindowInner* inner = doc->GetInnerWindow()) {
227 0 : inner->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
228 : }
229 0 : if (nsPIDOMWindowOuter* outer = doc->GetWindow()) {
230 0 : outer->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
231 : }
232 : }
233 : }
234 :
235 : void MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS,
236 : bool aPrepareForCC);
237 :
238 : void
239 0 : MarkSHEntry(nsISHEntry* aSHEntry, bool aCleanupJS, bool aPrepareForCC)
240 : {
241 0 : if (!aSHEntry) {
242 0 : return;
243 : }
244 :
245 0 : nsCOMPtr<nsIContentViewer> cview;
246 0 : aSHEntry->GetContentViewer(getter_AddRefs(cview));
247 0 : MarkContentViewer(cview, aCleanupJS, aPrepareForCC);
248 :
249 0 : nsCOMPtr<nsIDocShellTreeItem> child;
250 0 : int32_t i = 0;
251 0 : while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) &&
252 0 : child) {
253 0 : MarkDocShell(child, aCleanupJS, aPrepareForCC);
254 : }
255 :
256 0 : nsCOMPtr<nsISHContainer> shCont = do_QueryInterface(aSHEntry);
257 : int32_t count;
258 0 : shCont->GetChildCount(&count);
259 0 : for (i = 0; i < count; ++i) {
260 0 : nsCOMPtr<nsISHEntry> childEntry;
261 0 : shCont->GetChildAt(i, getter_AddRefs(childEntry));
262 0 : MarkSHEntry(childEntry, aCleanupJS, aPrepareForCC);
263 : }
264 :
265 : }
266 :
267 : void
268 0 : MarkDocShell(nsIDocShellTreeItem* aNode, bool aCleanupJS, bool aPrepareForCC)
269 : {
270 0 : nsCOMPtr<nsIDocShell> shell = do_QueryInterface(aNode);
271 0 : if (!shell) {
272 0 : return;
273 : }
274 :
275 0 : nsCOMPtr<nsIContentViewer> cview;
276 0 : shell->GetContentViewer(getter_AddRefs(cview));
277 0 : MarkContentViewer(cview, aCleanupJS, aPrepareForCC);
278 :
279 0 : nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell);
280 0 : nsCOMPtr<nsISHistory> history;
281 0 : webNav->GetSessionHistory(getter_AddRefs(history));
282 0 : if (history) {
283 : int32_t i, historyCount;
284 0 : history->GetCount(&historyCount);
285 0 : for (i = 0; i < historyCount; ++i) {
286 0 : nsCOMPtr<nsISHEntry> shEntry;
287 0 : history->GetEntryAtIndex(i, false, getter_AddRefs(shEntry));
288 :
289 0 : MarkSHEntry(shEntry, aCleanupJS, aPrepareForCC);
290 : }
291 : }
292 :
293 : int32_t i, childCount;
294 0 : aNode->GetChildCount(&childCount);
295 0 : for (i = 0; i < childCount; ++i) {
296 0 : nsCOMPtr<nsIDocShellTreeItem> child;
297 0 : aNode->GetChildAt(i, getter_AddRefs(child));
298 0 : MarkDocShell(child, aCleanupJS, aPrepareForCC);
299 : }
300 : }
301 :
302 : void
303 0 : MarkWindowList(nsISimpleEnumerator* aWindowList, bool aCleanupJS,
304 : bool aPrepareForCC)
305 : {
306 0 : nsCOMPtr<nsISupports> iter;
307 0 : while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) &&
308 0 : iter) {
309 0 : if (nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(iter)) {
310 0 : nsCOMPtr<nsIDocShell> rootDocShell = window->GetDocShell();
311 :
312 0 : MarkDocShell(rootDocShell, aCleanupJS, aPrepareForCC);
313 :
314 : nsCOMPtr<nsITabChild> tabChild =
315 0 : rootDocShell ? rootDocShell->GetTabChild() : nullptr;
316 0 : if (tabChild) {
317 0 : nsCOMPtr<nsIContentFrameMessageManager> mm;
318 0 : tabChild->GetMessageManager(getter_AddRefs(mm));
319 0 : if (mm) {
320 : // MarkForCC ends up calling UnmarkGray on message listeners, which
321 : // TraceBlackJS can't do yet.
322 0 : mm->MarkForCC();
323 : }
324 : }
325 : }
326 : }
327 0 : }
328 :
329 : nsresult
330 0 : nsCCUncollectableMarker::Observe(nsISupports* aSubject, const char* aTopic,
331 : const char16_t* aData)
332 : {
333 0 : if (!strcmp(aTopic, "xpcom-shutdown")) {
334 0 : Element::ClearContentUnbinder();
335 :
336 : nsCOMPtr<nsIObserverService> obs =
337 0 : mozilla::services::GetObserverService();
338 0 : if (!obs)
339 0 : return NS_ERROR_FAILURE;
340 :
341 : // No need for kungFuDeathGrip here, yay observerservice!
342 0 : obs->RemoveObserver(this, "xpcom-shutdown");
343 0 : obs->RemoveObserver(this, "cycle-collector-begin");
344 0 : obs->RemoveObserver(this, "cycle-collector-forget-skippable");
345 :
346 0 : sGeneration = 0;
347 :
348 0 : return NS_OK;
349 : }
350 :
351 0 : NS_ASSERTION(!strcmp(aTopic, "cycle-collector-begin") ||
352 : !strcmp(aTopic, "cycle-collector-forget-skippable"), "wrong topic");
353 :
354 : // JS cleanup can be slow. Do it only if there has been a GC.
355 : bool cleanupJS =
356 0 : nsJSContext::CleanupsSinceLastGC() == 0 &&
357 0 : !strcmp(aTopic, "cycle-collector-forget-skippable");
358 :
359 0 : bool prepareForCC = !strcmp(aTopic, "cycle-collector-begin");
360 0 : if (prepareForCC) {
361 0 : Element::ClearContentUnbinder();
362 : }
363 :
364 : // Increase generation to effectively unmark all current objects
365 0 : if (!++sGeneration) {
366 0 : ++sGeneration;
367 : }
368 :
369 0 : nsFocusManager::MarkUncollectableForCCGeneration(sGeneration);
370 :
371 : nsresult rv;
372 :
373 : // Iterate all toplevel windows
374 0 : nsCOMPtr<nsISimpleEnumerator> windowList;
375 : nsCOMPtr<nsIWindowMediator> med =
376 0 : do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
377 0 : if (med) {
378 0 : rv = med->GetEnumerator(nullptr, getter_AddRefs(windowList));
379 0 : NS_ENSURE_SUCCESS(rv, rv);
380 :
381 0 : MarkWindowList(windowList, cleanupJS, prepareForCC);
382 : }
383 :
384 : nsCOMPtr<nsIWindowWatcher> ww =
385 0 : do_GetService(NS_WINDOWWATCHER_CONTRACTID);
386 0 : if (ww) {
387 0 : rv = ww->GetWindowEnumerator(getter_AddRefs(windowList));
388 0 : NS_ENSURE_SUCCESS(rv, rv);
389 :
390 0 : MarkWindowList(windowList, cleanupJS, prepareForCC);
391 : }
392 :
393 : nsCOMPtr<nsIAppShellService> appShell =
394 0 : do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
395 0 : if (appShell) {
396 0 : nsCOMPtr<nsIXULWindow> hw;
397 0 : appShell->GetHiddenWindow(getter_AddRefs(hw));
398 0 : if (hw) {
399 0 : nsCOMPtr<nsIDocShell> shell;
400 0 : hw->GetDocShell(getter_AddRefs(shell));
401 0 : MarkDocShell(shell, cleanupJS, prepareForCC);
402 : }
403 0 : bool hasHiddenPrivateWindow = false;
404 0 : appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow);
405 0 : if (hasHiddenPrivateWindow) {
406 0 : appShell->GetHiddenPrivateWindow(getter_AddRefs(hw));
407 0 : if (hw) {
408 0 : nsCOMPtr<nsIDocShell> shell;
409 0 : hw->GetDocShell(getter_AddRefs(shell));
410 0 : MarkDocShell(shell, cleanupJS, prepareForCC);
411 : }
412 : }
413 : }
414 :
415 : #ifdef MOZ_XUL
416 0 : nsXULPrototypeCache* xulCache = nsXULPrototypeCache::GetInstance();
417 0 : if (xulCache) {
418 0 : xulCache->MarkInCCGeneration(sGeneration);
419 : }
420 : #endif
421 :
422 : enum ForgetSkippableCleanupState
423 : {
424 : eInitial = 0,
425 : eUnmarkJSEventListeners = 1,
426 : eUnmarkMessageManagers = 2,
427 : eUnmarkStrongObservers = 3,
428 : eUnmarkJSHolders = 4,
429 : eDone = 5
430 : };
431 :
432 : static_assert(eDone == NS_MAJOR_FORGET_SKIPPABLE_CALLS,
433 : "There must be one forgetSkippable call per cleanup state.");
434 :
435 : static uint32_t sFSState = eDone;
436 0 : if (prepareForCC) {
437 0 : sFSState = eDone;
438 0 : return NS_OK;
439 : }
440 :
441 0 : if (cleanupJS) {
442 : // After a GC we start clean up phases from the beginning,
443 : // but we don't want to do the additional clean up phases here
444 : // since we have done already plenty of gray unmarking while going through
445 : // frame message managers and docshells.
446 0 : sFSState = eInitial;
447 0 : return NS_OK;
448 : } else {
449 0 : ++sFSState;
450 : }
451 :
452 0 : switch(sFSState) {
453 : case eUnmarkJSEventListeners: {
454 0 : nsContentUtils::UnmarkGrayJSListenersInCCGenerationDocuments();
455 0 : break;
456 : }
457 : case eUnmarkMessageManagers: {
458 0 : MarkMessageManagers();
459 0 : break;
460 : }
461 : case eUnmarkStrongObservers: {
462 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
463 0 : static_cast<nsObserverService *>(obs.get())->UnmarkGrayStrongObservers();
464 0 : break;
465 : }
466 : case eUnmarkJSHolders: {
467 0 : xpc_UnmarkSkippableJSHolders();
468 0 : break;
469 : }
470 : default: {
471 0 : break;
472 : }
473 : }
474 :
475 0 : return NS_OK;
476 : }
477 :
478 : void
479 1 : mozilla::dom::TraceBlackJS(JSTracer* aTrc, uint32_t aGCNumber, bool aIsShutdownGC)
480 : {
481 : #ifdef MOZ_XUL
482 : // Mark the scripts held in the XULPrototypeCache. This is required to keep
483 : // the JS script in the cache live across GC.
484 1 : nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance();
485 1 : if (cache) {
486 1 : if (aIsShutdownGC) {
487 0 : cache->FlushScripts();
488 : } else {
489 1 : cache->MarkInGC(aTrc);
490 : }
491 : }
492 : #endif
493 :
494 1 : if (!nsCCUncollectableMarker::sGeneration) {
495 0 : return;
496 : }
497 :
498 1 : if (nsFrameMessageManager::GetChildProcessManager()) {
499 1 : nsIContentProcessMessageManager* pg = ProcessGlobal::Get();
500 1 : if (pg) {
501 1 : mozilla::TraceScriptHolder(pg, aTrc);
502 : }
503 : }
504 :
505 : // Mark globals of active windows black.
506 : nsGlobalWindow::WindowByIdTable* windowsById =
507 1 : nsGlobalWindow::GetWindowsTable();
508 1 : if (windowsById) {
509 10 : for (auto iter = windowsById->Iter(); !iter.Done(); iter.Next()) {
510 9 : nsGlobalWindow* window = iter.Data();
511 9 : if (window->GetDocShell() && window->IsOuterWindow()) {
512 3 : window->TraceGlobalJSObject(aTrc);
513 3 : EventListenerManager* elm = window->GetExistingListenerManager();
514 3 : if (elm) {
515 1 : elm->TraceListeners(aTrc);
516 : }
517 :
518 3 : if (window->IsRootOuterWindow()) {
519 : // In child process trace all the TabChildGlobals.
520 : // Since there is one root outer window per TabChildGlobal, we need
521 : // to look for only those windows, not all.
522 2 : nsIDocShell* ds = window->GetDocShell();
523 2 : if (ds) {
524 4 : nsCOMPtr<nsITabChild> tabChild = ds->GetTabChild();
525 2 : if (tabChild) {
526 0 : nsCOMPtr<nsIContentFrameMessageManager> mm;
527 0 : tabChild->GetMessageManager(getter_AddRefs(mm));
528 0 : nsCOMPtr<EventTarget> et = do_QueryInterface(mm);
529 0 : if (et) {
530 : nsCOMPtr<nsISupports> tabChildAsSupports =
531 0 : do_QueryInterface(tabChild);
532 0 : mozilla::TraceScriptHolder(tabChildAsSupports, aTrc);
533 0 : EventListenerManager* elm = et->GetExistingListenerManager();
534 0 : if (elm) {
535 0 : elm->TraceListeners(aTrc);
536 : }
537 : // As of now there isn't an easy way to trace message listeners.
538 : }
539 : }
540 : }
541 : }
542 :
543 : #ifdef MOZ_XUL
544 3 : nsIDocument* doc = window->GetExtantDoc();
545 3 : if (doc && doc->IsXULDocument()) {
546 1 : XULDocument* xulDoc = static_cast<XULDocument*>(doc);
547 1 : xulDoc->TraceProtos(aTrc, aGCNumber);
548 : }
549 : #endif
550 : }
551 : }
552 : }
553 : }
|