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 "mozilla/dom/BrowsingContextGroup.h"
8 
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/InputTaskManager.h"
11 #include "mozilla/dom/BrowsingContextBinding.h"
12 #include "mozilla/dom/BindingUtils.h"
13 #include "mozilla/dom/ContentChild.h"
14 #include "mozilla/dom/ContentParent.h"
15 #include "mozilla/dom/DocGroup.h"
16 #include "mozilla/StaticPrefs_dom.h"
17 #include "mozilla/ThrottledEventQueue.h"
18 #include "nsFocusManager.h"
19 #include "nsTHashMap.h"
20 
21 namespace mozilla {
22 namespace dom {
23 
24 static StaticRefPtr<BrowsingContextGroup> sChromeGroup;
25 
26 static StaticAutoPtr<nsTHashMap<uint64_t, RefPtr<BrowsingContextGroup>>>
27     sBrowsingContextGroups;
28 
GetOrCreate(uint64_t aId)29 already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetOrCreate(
30     uint64_t aId) {
31   if (!sBrowsingContextGroups) {
32     sBrowsingContextGroups =
33         new nsTHashMap<nsUint64HashKey, RefPtr<BrowsingContextGroup>>();
34     ClearOnShutdown(&sBrowsingContextGroups);
35   }
36 
37   return do_AddRef(sBrowsingContextGroups->LookupOrInsertWith(
38       aId, [&aId] { return do_AddRef(new BrowsingContextGroup(aId)); }));
39 }
40 
GetExisting(uint64_t aId)41 already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetExisting(
42     uint64_t aId) {
43   if (sBrowsingContextGroups) {
44     return do_AddRef(sBrowsingContextGroups->Get(aId));
45   }
46   return nullptr;
47 }
48 
Create()49 already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Create() {
50   return GetOrCreate(nsContentUtils::GenerateBrowsingContextId());
51 }
52 
BrowsingContextGroup(uint64_t aId)53 BrowsingContextGroup::BrowsingContextGroup(uint64_t aId) : mId(aId) {
54   mTimerEventQueue = ThrottledEventQueue::Create(
55       GetMainThreadSerialEventTarget(), "BrowsingContextGroup timer queue");
56 
57   mWorkerEventQueue = ThrottledEventQueue::Create(
58       GetMainThreadSerialEventTarget(), "BrowsingContextGroup worker queue");
59 }
60 
Register(nsISupports * aContext)61 void BrowsingContextGroup::Register(nsISupports* aContext) {
62   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
63   MOZ_DIAGNOSTIC_ASSERT(aContext);
64   mContexts.Insert(aContext);
65 }
66 
Unregister(nsISupports * aContext)67 void BrowsingContextGroup::Unregister(nsISupports* aContext) {
68   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
69   MOZ_DIAGNOSTIC_ASSERT(aContext);
70   mContexts.Remove(aContext);
71 
72   MaybeDestroy();
73 }
74 
EnsureHostProcess(ContentParent * aProcess)75 void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) {
76   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
77   MOZ_DIAGNOSTIC_ASSERT(this != sChromeGroup,
78                         "cannot have content host for chrome group");
79   MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE,
80                         "cannot use preallocated process as host");
81   MOZ_DIAGNOSTIC_ASSERT(!aProcess->GetRemoteType().IsEmpty(),
82                         "host process must have remote type");
83 
84   if (aProcess->IsDead() ||
85       mHosts.WithEntryHandle(aProcess->GetRemoteType(), [&](auto&& entry) {
86         if (entry) {
87           MOZ_DIAGNOSTIC_ASSERT(
88               entry.Data() == aProcess,
89               "There's already another host process for this remote type");
90           return false;
91         }
92 
93         // This process wasn't already marked as our host, so insert it, and
94         // begin subscribing, unless the process is still launching.
95         entry.Insert(do_AddRef(aProcess));
96 
97         return true;
98       })) {
99     aProcess->AddBrowsingContextGroup(this);
100   }
101 }
102 
RemoveHostProcess(ContentParent * aProcess)103 void BrowsingContextGroup::RemoveHostProcess(ContentParent* aProcess) {
104   MOZ_DIAGNOSTIC_ASSERT(aProcess);
105   MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
106   auto entry = mHosts.Lookup(aProcess->GetRemoteType());
107   if (entry && entry.Data() == aProcess) {
108     entry.Remove();
109   }
110 }
111 
CollectContextInitializers(Span<RefPtr<BrowsingContext>> aContexts,nsTArray<SyncedContextInitializer> & aInits)112 static void CollectContextInitializers(
113     Span<RefPtr<BrowsingContext>> aContexts,
114     nsTArray<SyncedContextInitializer>& aInits) {
115   // The order that we record these initializers is important, as it will keep
116   // the order that children are attached to their parent in the newly connected
117   // content process consistent.
118   for (auto& context : aContexts) {
119     aInits.AppendElement(context->GetIPCInitializer());
120     for (const auto& window : context->GetWindowContexts()) {
121       aInits.AppendElement(window->GetIPCInitializer());
122       CollectContextInitializers(window->Children(), aInits);
123     }
124   }
125 }
126 
Subscribe(ContentParent * aProcess)127 void BrowsingContextGroup::Subscribe(ContentParent* aProcess) {
128   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
129   MOZ_DIAGNOSTIC_ASSERT(aProcess && !aProcess->IsLaunching());
130   MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
131 
132   // Check if we're already subscribed to this process.
133   if (!mSubscribers.EnsureInserted(aProcess)) {
134     return;
135   }
136 
137 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
138   // If the process is already marked as dead, we won't be the host, but may
139   // still need to subscribe to the process due to creating a popup while
140   // shutting down.
141   if (!aProcess->IsDead()) {
142     auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType());
143     MOZ_DIAGNOSTIC_ASSERT(hostEntry && hostEntry.Data() == aProcess,
144                           "Cannot subscribe a non-host process");
145   }
146 #endif
147 
148   // FIXME: This won't send non-discarded children of discarded BCs, but those
149   // BCs will be in the process of being destroyed anyway.
150   // FIXME: Prevent that situation from occuring.
151   nsTArray<SyncedContextInitializer> inits(mContexts.Count());
152   CollectContextInitializers(mToplevels, inits);
153 
154   // Send all of our contexts to the target content process.
155   Unused << aProcess->SendRegisterBrowsingContextGroup(Id(), inits);
156 
157   // If the focused or active BrowsingContexts belong in this group, tell the
158   // newly subscribed process.
159   if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
160     BrowsingContext* focused = fm->GetFocusedBrowsingContextInChrome();
161     if (focused && focused->Group() != this) {
162       focused = nullptr;
163     }
164     BrowsingContext* active = fm->GetActiveBrowsingContextInChrome();
165     if (active && active->Group() != this) {
166       active = nullptr;
167     }
168 
169     if (focused || active) {
170       Unused << aProcess->SendSetupFocusedAndActive(
171           focused, fm->GetActionIdForFocusedBrowsingContextInChrome(), active,
172           fm->GetActionIdForActiveBrowsingContextInChrome());
173     }
174   }
175 }
176 
Unsubscribe(ContentParent * aProcess)177 void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) {
178   MOZ_DIAGNOSTIC_ASSERT(aProcess);
179   MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
180   mSubscribers.Remove(aProcess);
181   aProcess->RemoveBrowsingContextGroup(this);
182 
183 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
184   auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType());
185   MOZ_DIAGNOSTIC_ASSERT(!hostEntry || hostEntry.Data() != aProcess,
186                         "Unsubscribing existing host entry");
187 #endif
188 }
189 
GetHostProcess(const nsACString & aRemoteType)190 ContentParent* BrowsingContextGroup::GetHostProcess(
191     const nsACString& aRemoteType) {
192   return mHosts.GetWeak(aRemoteType);
193 }
194 
UpdateToplevelsSuspendedIfNeeded()195 void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() {
196   if (!StaticPrefs::dom_suspend_inactive_enabled()) {
197     return;
198   }
199 
200   mToplevelsSuspended = ShouldSuspendAllTopLevelContexts();
201   for (const auto& context : mToplevels) {
202     nsPIDOMWindowOuter* outer = context->GetDOMWindow();
203     if (!outer) {
204       continue;
205     }
206     nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
207     if (!inner) {
208       continue;
209     }
210     if (mToplevelsSuspended && !inner->GetWasSuspendedByGroup()) {
211       inner->Suspend();
212       inner->SetWasSuspendedByGroup(true);
213     } else if (!mToplevelsSuspended && inner->GetWasSuspendedByGroup()) {
214       inner->Resume();
215       inner->SetWasSuspendedByGroup(false);
216     }
217   }
218 }
219 
ShouldSuspendAllTopLevelContexts() const220 bool BrowsingContextGroup::ShouldSuspendAllTopLevelContexts() const {
221   for (const auto& context : mToplevels) {
222     if (!context->InactiveForSuspend()) {
223       return false;
224     }
225   }
226   return true;
227 }
228 
~BrowsingContextGroup()229 BrowsingContextGroup::~BrowsingContextGroup() { Destroy(); }
230 
Destroy()231 void BrowsingContextGroup::Destroy() {
232 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
233   if (mDestroyed) {
234     MOZ_DIAGNOSTIC_ASSERT(mHosts.Count() == 0);
235     MOZ_DIAGNOSTIC_ASSERT(mSubscribers.Count() == 0);
236     MOZ_DIAGNOSTIC_ASSERT_IF(sBrowsingContextGroups,
237                              !sBrowsingContextGroups->Contains(Id()) ||
238                                  *sBrowsingContextGroups->Lookup(Id()) != this);
239   }
240   mDestroyed = true;
241 #endif
242 
243   // Make sure to call `RemoveBrowsingContextGroup` for every entry in both
244   // `mHosts` and `mSubscribers`. This will visit most entries twice, but
245   // `RemoveBrowsingContextGroup` is safe to call multiple times.
246   for (const auto& entry : mHosts.Values()) {
247     entry->RemoveBrowsingContextGroup(this);
248   }
249   for (const auto& key : mSubscribers) {
250     key->RemoveBrowsingContextGroup(this);
251   }
252   mHosts.Clear();
253   mSubscribers.Clear();
254 
255   if (sBrowsingContextGroups) {
256     sBrowsingContextGroups->Remove(Id());
257   }
258 }
259 
AddKeepAlive()260 void BrowsingContextGroup::AddKeepAlive() {
261   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
262   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
263   mKeepAliveCount++;
264 }
265 
RemoveKeepAlive()266 void BrowsingContextGroup::RemoveKeepAlive() {
267   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
268   MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0);
269   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
270   mKeepAliveCount--;
271 
272   MaybeDestroy();
273 }
274 
MakeKeepAlivePtr()275 auto BrowsingContextGroup::MakeKeepAlivePtr() -> KeepAlivePtr {
276   AddKeepAlive();
277   return KeepAlivePtr{do_AddRef(this).take()};
278 }
279 
MaybeDestroy()280 void BrowsingContextGroup::MaybeDestroy() {
281   // Once there are no synced contexts referencing a `BrowsingContextGroup`, we
282   // can clear subscribers and destroy this group. We only do this in the parent
283   // process, as it will orchestrate destruction of BCGs in content processes.
284   if (XRE_IsParentProcess() && mContexts.IsEmpty() && mKeepAliveCount == 0 &&
285       this != sChromeGroup) {
286     Destroy();
287 
288     // We may have been deleted here, as `Destroy()` will clear references. Do
289     // not access any members at this point.
290   }
291 }
292 
ChildDestroy()293 void BrowsingContextGroup::ChildDestroy() {
294   MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
295   MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
296   MOZ_DIAGNOSTIC_ASSERT(mContexts.IsEmpty());
297   Destroy();
298 }
299 
GetParentObject() const300 nsISupports* BrowsingContextGroup::GetParentObject() const {
301   return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
302 }
303 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)304 JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx,
305                                            JS::Handle<JSObject*> aGivenProto) {
306   return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto);
307 }
308 
QueuePostMessageEvent(already_AddRefed<nsIRunnable> && aRunnable)309 nsresult BrowsingContextGroup::QueuePostMessageEvent(
310     already_AddRefed<nsIRunnable>&& aRunnable) {
311   if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
312     if (!mPostMessageEventQueue) {
313       nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
314       mPostMessageEventQueue = ThrottledEventQueue::Create(
315           target, "PostMessage Queue",
316           nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS);
317       nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
318       MOZ_ALWAYS_SUCCEEDS(rv);
319     }
320 
321     // Ensure the queue is enabled
322     if (mPostMessageEventQueue->IsPaused()) {
323       nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
324       MOZ_ALWAYS_SUCCEEDS(rv);
325     }
326 
327     if (mPostMessageEventQueue) {
328       mPostMessageEventQueue->Dispatch(std::move(aRunnable),
329                                        NS_DISPATCH_NORMAL);
330       return NS_OK;
331     }
332   }
333   return NS_ERROR_FAILURE;
334 }
335 
FlushPostMessageEvents()336 void BrowsingContextGroup::FlushPostMessageEvents() {
337   if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
338     if (mPostMessageEventQueue) {
339       nsresult rv = mPostMessageEventQueue->SetIsPaused(true);
340       MOZ_ALWAYS_SUCCEEDS(rv);
341       nsCOMPtr<nsIRunnable> event;
342       while ((event = mPostMessageEventQueue->GetEvent())) {
343         NS_DispatchToMainThread(event.forget());
344       }
345     }
346   }
347 }
348 
HasActiveBC()349 bool BrowsingContextGroup::HasActiveBC() {
350   for (auto& topLevelBC : Toplevels()) {
351     if (topLevelBC->IsActive()) {
352       return true;
353     }
354   }
355   return false;
356 }
357 
IncInputEventSuspensionLevel()358 void BrowsingContextGroup::IncInputEventSuspensionLevel() {
359   MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
360   if (!mHasIncreasedInputTaskManagerSuspensionLevel && HasActiveBC()) {
361     IncInputTaskManagerSuspensionLevel();
362   }
363   ++mInputEventSuspensionLevel;
364 }
365 
DecInputEventSuspensionLevel()366 void BrowsingContextGroup::DecInputEventSuspensionLevel() {
367   MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
368   --mInputEventSuspensionLevel;
369   if (!mInputEventSuspensionLevel &&
370       mHasIncreasedInputTaskManagerSuspensionLevel) {
371     DecInputTaskManagerSuspensionLevel();
372   }
373 }
374 
DecInputTaskManagerSuspensionLevel()375 void BrowsingContextGroup::DecInputTaskManagerSuspensionLevel() {
376   MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
377   MOZ_ASSERT(mHasIncreasedInputTaskManagerSuspensionLevel);
378 
379   InputTaskManager::Get()->DecSuspensionLevel();
380   mHasIncreasedInputTaskManagerSuspensionLevel = false;
381 }
382 
IncInputTaskManagerSuspensionLevel()383 void BrowsingContextGroup::IncInputTaskManagerSuspensionLevel() {
384   MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
385   MOZ_ASSERT(!mHasIncreasedInputTaskManagerSuspensionLevel);
386   MOZ_ASSERT(HasActiveBC());
387 
388   InputTaskManager::Get()->IncSuspensionLevel();
389   mHasIncreasedInputTaskManagerSuspensionLevel = true;
390 }
391 
UpdateInputTaskManagerIfNeeded(bool aIsActive)392 void BrowsingContextGroup::UpdateInputTaskManagerIfNeeded(bool aIsActive) {
393   MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
394   if (!aIsActive) {
395     if (mHasIncreasedInputTaskManagerSuspensionLevel) {
396       MOZ_ASSERT(mInputEventSuspensionLevel > 0);
397       if (!HasActiveBC()) {
398         DecInputTaskManagerSuspensionLevel();
399       }
400     }
401   } else {
402     if (mInputEventSuspensionLevel &&
403         !mHasIncreasedInputTaskManagerSuspensionLevel) {
404       IncInputTaskManagerSuspensionLevel();
405     }
406   }
407 }
408 
409 /* static */
GetChromeGroup()410 BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() {
411   MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
412   if (!sChromeGroup && XRE_IsParentProcess()) {
413     sChromeGroup = BrowsingContextGroup::Create();
414     ClearOnShutdown(&sChromeGroup);
415   }
416 
417   return sChromeGroup;
418 }
419 
GetDocGroups(nsTArray<DocGroup * > & aDocGroups)420 void BrowsingContextGroup::GetDocGroups(nsTArray<DocGroup*>& aDocGroups) {
421   MOZ_ASSERT(NS_IsMainThread());
422   AppendToArray(aDocGroups, mDocGroups.Values());
423 }
424 
AddDocument(const nsACString & aKey,Document * aDocument)425 already_AddRefed<DocGroup> BrowsingContextGroup::AddDocument(
426     const nsACString& aKey, Document* aDocument) {
427   MOZ_ASSERT(NS_IsMainThread());
428 
429   RefPtr<DocGroup>& docGroup = mDocGroups.LookupOrInsertWith(
430       aKey, [&] { return DocGroup::Create(this, aKey); });
431 
432   docGroup->AddDocument(aDocument);
433   return do_AddRef(docGroup);
434 }
435 
RemoveDocument(Document * aDocument,DocGroup * aDocGroup)436 void BrowsingContextGroup::RemoveDocument(Document* aDocument,
437                                           DocGroup* aDocGroup) {
438   MOZ_ASSERT(NS_IsMainThread());
439   RefPtr<DocGroup> docGroup = aDocGroup;
440   // Removing the last document in DocGroup might decrement the
441   // DocGroup BrowsingContextGroup's refcount to 0.
442   RefPtr<BrowsingContextGroup> kungFuDeathGrip(this);
443   docGroup->RemoveDocument(aDocument);
444 
445   if (docGroup->IsEmpty()) {
446     mDocGroups.Remove(docGroup->GetKey());
447   }
448 }
449 
Select(WindowContext * aParent,BrowsingContext * aOpener)450 already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Select(
451     WindowContext* aParent, BrowsingContext* aOpener) {
452   if (aParent) {
453     return do_AddRef(aParent->Group());
454   }
455   if (aOpener) {
456     return do_AddRef(aOpener->Group());
457   }
458   return Create();
459 }
460 
GetAllGroups(nsTArray<RefPtr<BrowsingContextGroup>> & aGroups)461 void BrowsingContextGroup::GetAllGroups(
462     nsTArray<RefPtr<BrowsingContextGroup>>& aGroups) {
463   aGroups.Clear();
464   if (!sBrowsingContextGroups) {
465     return;
466   }
467 
468   aGroups = ToArray(sBrowsingContextGroups->Values());
469 }
470 
471 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts,
472                                       mToplevels, mHosts, mSubscribers,
473                                       mTimerEventQueue, mWorkerEventQueue,
474                                       mDocGroups)
475 
476 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContextGroup, AddRef)
477 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContextGroup, Release)
478 
479 }  // namespace dom
480 }  // namespace mozilla
481