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