Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / dom / audiochannel / AudioChannelService.cpp
bloba1a24100668a4393fdbb81c2007091a40be240fd
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AudioChannelService.h"
9 #include "base/basictypes.h"
11 #include "mozilla/Services.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/dom/Document.h"
16 #include "nsContentUtils.h"
17 #include "nsIObserverService.h"
18 #include "nsISupportsPrimitives.h"
19 #include "nsThreadUtils.h"
20 #include "nsHashPropertyBag.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsServiceManagerUtils.h"
25 #include "mozilla/Preferences.h"
27 using namespace mozilla;
28 using namespace mozilla::dom;
30 mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
32 namespace {
34 bool sXPCOMShuttingDown = false;
36 class AudioPlaybackRunnable final : public Runnable {
37 public:
38 AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
39 AudioChannelService::AudibleChangedReasons aReason)
40 : mozilla::Runnable("AudioPlaybackRunnable"),
41 mWindow(aWindow),
42 mActive(aActive),
43 mReason(aReason) {}
45 NS_IMETHOD Run() override {
46 nsCOMPtr<nsIObserverService> observerService =
47 services::GetObserverService();
48 if (NS_WARN_IF(!observerService)) {
49 return NS_ERROR_FAILURE;
52 nsAutoString state;
53 GetActiveState(state);
55 observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
56 state.get());
58 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
59 ("AudioPlaybackRunnable, active = %s, reason = %s\n",
60 mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
62 return NS_OK;
65 private:
66 void GetActiveState(nsAString& aState) {
67 if (mActive) {
68 aState.AssignLiteral("active");
69 } else {
70 if (mReason ==
71 AudioChannelService::AudibleChangedReasons::ePauseStateChanged) {
72 aState.AssignLiteral("inactive-pause");
73 } else {
74 aState.AssignLiteral("inactive-nonaudible");
79 nsCOMPtr<nsPIDOMWindowOuter> mWindow;
80 bool mActive;
81 AudioChannelService::AudibleChangedReasons mReason;
84 } // anonymous namespace
86 namespace mozilla::dom {
88 const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend) {
89 MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
90 aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK);
92 switch (aSuspend) {
93 case nsISuspendedTypes::NONE_SUSPENDED:
94 return "none";
95 case nsISuspendedTypes::SUSPENDED_BLOCK:
96 return "block";
97 default:
98 return "unknown";
102 const char* AudibleStateToStr(
103 const AudioChannelService::AudibleState& aAudible) {
104 MOZ_ASSERT(aAudible == AudioChannelService::AudibleState::eNotAudible ||
105 aAudible == AudioChannelService::AudibleState::eMaybeAudible ||
106 aAudible == AudioChannelService::AudibleState::eAudible);
108 switch (aAudible) {
109 case AudioChannelService::AudibleState::eNotAudible:
110 return "not-audible";
111 case AudioChannelService::AudibleState::eMaybeAudible:
112 return "maybe-audible";
113 case AudioChannelService::AudibleState::eAudible:
114 return "audible";
115 default:
116 return "unknown";
120 const char* AudibleChangedReasonToStr(
121 const AudioChannelService::AudibleChangedReasons& aReason) {
122 MOZ_ASSERT(
123 aReason == AudioChannelService::AudibleChangedReasons::eVolumeChanged ||
124 aReason ==
125 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged ||
126 aReason ==
127 AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
129 switch (aReason) {
130 case AudioChannelService::AudibleChangedReasons::eVolumeChanged:
131 return "volume";
132 case AudioChannelService::AudibleChangedReasons::eDataAudibleChanged:
133 return "data-audible";
134 case AudioChannelService::AudibleChangedReasons::ePauseStateChanged:
135 return "pause-state";
136 default:
137 return "unknown";
141 StaticRefPtr<AudioChannelService> gAudioChannelService;
143 /* static */
144 void AudioChannelService::CreateServiceIfNeeded() {
145 MOZ_ASSERT(NS_IsMainThread());
147 if (!gAudioChannelService) {
148 gAudioChannelService = new AudioChannelService();
152 /* static */
153 already_AddRefed<AudioChannelService> AudioChannelService::GetOrCreate() {
154 if (sXPCOMShuttingDown) {
155 return nullptr;
158 CreateServiceIfNeeded();
159 RefPtr<AudioChannelService> service = gAudioChannelService.get();
160 return service.forget();
163 /* static */
164 already_AddRefed<AudioChannelService> AudioChannelService::Get() {
165 if (sXPCOMShuttingDown) {
166 return nullptr;
169 RefPtr<AudioChannelService> service = gAudioChannelService.get();
170 return service.forget();
173 /* static */
174 LogModule* AudioChannelService::GetAudioChannelLog() {
175 return gAudioChannelLog;
178 /* static */
179 void AudioChannelService::Shutdown() {
180 if (gAudioChannelService) {
181 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
182 if (obs) {
183 obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown");
184 obs->RemoveObserver(gAudioChannelService, "outer-window-destroyed");
187 gAudioChannelService->mWindows.Clear();
189 gAudioChannelService = nullptr;
193 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
194 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
195 NS_INTERFACE_MAP_ENTRY(nsIObserver)
196 NS_INTERFACE_MAP_END
198 NS_IMPL_ADDREF(AudioChannelService)
199 NS_IMPL_RELEASE(AudioChannelService)
201 AudioChannelService::AudioChannelService() {
202 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
203 if (obs) {
204 obs->AddObserver(this, "xpcom-shutdown", false);
205 obs->AddObserver(this, "outer-window-destroyed", false);
209 AudioChannelService::~AudioChannelService() = default;
211 void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
212 AudibleState aAudible) {
213 MOZ_ASSERT(aAgent);
215 uint64_t windowID = aAgent->WindowID();
216 AudioChannelWindow* winData = GetWindowData(windowID);
217 if (!winData) {
218 winData = new AudioChannelWindow(windowID);
219 mWindows.AppendElement(WrapUnique(winData));
222 // To make sure agent would be alive because AppendAgent() would trigger the
223 // callback function of AudioChannelAgentOwner that means the agent might be
224 // released in their callback.
225 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
226 winData->AppendAgent(aAgent, aAudible);
229 void AudioChannelService::UnregisterAudioChannelAgent(
230 AudioChannelAgent* aAgent) {
231 MOZ_ASSERT(aAgent);
233 AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
234 if (!winData) {
235 return;
238 // To make sure agent would be alive because AppendAgent() would trigger the
239 // callback function of AudioChannelAgentOwner that means the agent might be
240 // released in their callback.
241 RefPtr<AudioChannelAgent> kungFuDeathGrip(aAgent);
242 winData->RemoveAgent(aAgent);
245 AudioPlaybackConfig AudioChannelService::GetMediaConfig(
246 nsPIDOMWindowOuter* aWindow) const {
247 AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
249 if (!aWindow) {
250 config.mVolume = 0.0;
251 config.mMuted = true;
252 config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
253 return config;
256 AudioChannelWindow* winData = nullptr;
257 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
259 // The volume must be calculated based on the window hierarchy. Here we go up
260 // to the top window and we calculate the volume and the muted flag.
261 do {
262 winData = GetWindowData(window->WindowID());
263 if (winData) {
264 config.mVolume *= winData->mConfig.mVolume;
265 config.mMuted = config.mMuted || winData->mConfig.mMuted;
266 config.mCapturedAudio = winData->mIsAudioCaptured;
269 config.mMuted = config.mMuted || window->GetAudioMuted();
270 if (window->ShouldDelayMediaFromStart()) {
271 config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
274 nsCOMPtr<nsPIDOMWindowOuter> win =
275 window->GetInProcessScriptableParentOrNull();
276 if (!win) {
277 break;
280 window = win;
282 // If there is no parent, or we are the toplevel we don't continue.
283 } while (window && window != aWindow);
285 return config;
288 void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
289 AudibleState aAudible,
290 AudibleChangedReasons aReason) {
291 MOZ_ASSERT(aAgent);
293 uint64_t windowID = aAgent->WindowID();
294 AudioChannelWindow* winData = GetWindowData(windowID);
295 if (winData) {
296 winData->AudioAudibleChanged(aAgent, aAudible, aReason);
300 NS_IMETHODIMP
301 AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic,
302 const char16_t* aData) {
303 if (!strcmp(aTopic, "xpcom-shutdown")) {
304 sXPCOMShuttingDown = true;
305 Shutdown();
306 } else if (!strcmp(aTopic, "outer-window-destroyed")) {
307 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
308 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
310 uint64_t outerID;
311 nsresult rv = wrapper->GetData(&outerID);
312 if (NS_WARN_IF(NS_FAILED(rv))) {
313 return rv;
316 UniquePtr<AudioChannelWindow> winData;
318 nsTObserverArray<UniquePtr<AudioChannelWindow>>::ForwardIterator iter(
319 mWindows);
320 while (iter.HasMore()) {
321 auto& next = iter.GetNext();
322 if (next->mWindowID == outerID) {
323 winData = std::move(next);
324 iter.Remove();
325 break;
330 if (winData) {
331 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
332 agent->WindowVolumeChanged(winData->mConfig.mVolume,
333 winData->mConfig.mMuted);
338 return NS_OK;
341 void AudioChannelService::RefreshAgents(
342 nsPIDOMWindowOuter* aWindow,
343 const std::function<void(AudioChannelAgent*)>& aFunc) {
344 MOZ_ASSERT(aWindow);
346 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
347 if (!topWindow) {
348 return;
351 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
352 if (!winData) {
353 return;
356 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
357 aFunc(agent);
361 void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
362 float aVolume, bool aMuted) {
363 RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
364 agent->WindowVolumeChanged(aVolume, aMuted);
368 void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
369 nsSuspendedTypes aSuspend) {
370 RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
371 agent->WindowSuspendChanged(aSuspend);
375 void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
376 uint64_t aInnerWindowID,
377 bool aCapture) {
378 MOZ_ASSERT(NS_IsMainThread());
379 MOZ_ASSERT(aWindow);
381 MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
382 ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
383 "aCapture = %d\n",
384 aWindow, aCapture));
386 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
387 if (!topWindow) {
388 return;
391 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
393 // This can happen, but only during shutdown, because the the outer window
394 // changes ScriptableTop, so that its ID is different.
395 // In this case either we are capturing, and it's too late because the window
396 // has been closed anyways, or we are un-capturing, and everything has already
397 // been cleaned up by the HTMLMediaElements or the AudioContexts.
398 if (!winData) {
399 return;
402 if (aCapture != winData->mIsAudioCaptured) {
403 winData->mIsAudioCaptured = aCapture;
404 for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
405 agent->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
410 AudioChannelService::AudioChannelWindow*
411 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
412 MOZ_ASSERT(NS_IsMainThread());
413 MOZ_ASSERT(aWindow);
415 AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
416 if (!winData) {
417 winData = new AudioChannelWindow(aWindow->WindowID());
418 mWindows.AppendElement(WrapUnique(winData));
421 return winData;
424 AudioChannelService::AudioChannelWindow* AudioChannelService::GetWindowData(
425 uint64_t aWindowID) const {
426 const auto [begin, end] = mWindows.NonObservingRange();
427 const auto foundIt = std::find_if(begin, end, [aWindowID](const auto& next) {
428 return next->mWindowID == aWindowID;
430 return foundIt != end ? foundIt->get() : nullptr;
433 bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
434 MOZ_ASSERT(NS_IsMainThread());
436 auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
437 if (!window) {
438 return false;
441 AudioChannelWindow* winData = GetWindowData(window->WindowID());
442 if (!winData) {
443 return false;
446 return !winData->mAudibleAgents.IsEmpty();
449 void AudioChannelService::NotifyResumingDelayedMedia(
450 nsPIDOMWindowOuter* aWindow) {
451 MOZ_ASSERT(aWindow);
453 nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
454 if (!topWindow) {
455 return;
458 AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
459 if (!winData) {
460 return;
463 winData->NotifyMediaBlockStop(aWindow);
464 RefreshAgentsSuspend(aWindow, nsISuspendedTypes::NONE_SUSPENDED);
467 void AudioChannelService::AudioChannelWindow::AppendAgent(
468 AudioChannelAgent* aAgent, AudibleState aAudible) {
469 MOZ_ASSERT(aAgent);
471 AppendAgentAndIncreaseAgentsNum(aAgent);
472 AudioAudibleChanged(aAgent, aAudible,
473 AudibleChangedReasons::eDataAudibleChanged);
476 void AudioChannelService::AudioChannelWindow::RemoveAgent(
477 AudioChannelAgent* aAgent) {
478 MOZ_ASSERT(aAgent);
480 RemoveAgentAndReduceAgentsNum(aAgent);
481 AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
482 AudibleChangedReasons::ePauseStateChanged);
485 void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
486 nsPIDOMWindowOuter* aWindow) {
487 if (mShouldSendActiveMediaBlockStopEvent) {
488 mShouldSendActiveMediaBlockStopEvent = false;
489 nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
490 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
491 "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
492 [window]() -> void {
493 nsCOMPtr<nsIObserverService> observerService =
494 services::GetObserverService();
495 if (NS_WARN_IF(!observerService)) {
496 return;
499 observerService->NotifyObservers(ToSupports(window), "audio-playback",
500 u"activeMediaBlockStop");
501 }));
505 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
506 AudioChannelAgent* aAgent) {
507 MOZ_ASSERT(aAgent);
508 MOZ_ASSERT(!mAgents.Contains(aAgent));
510 mAgents.AppendElement(aAgent);
512 ++mConfig.mNumberOfAgents;
515 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
516 AudioChannelAgent* aAgent) {
517 MOZ_ASSERT(aAgent);
518 MOZ_ASSERT(mAgents.Contains(aAgent));
520 mAgents.RemoveElement(aAgent);
522 MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
523 --mConfig.mNumberOfAgents;
526 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
527 AudioChannelAgent* aAgent, AudibleState aAudible,
528 AudibleChangedReasons aReason) {
529 MOZ_ASSERT(aAgent);
531 if (aAudible == AudibleState::eAudible) {
532 AppendAudibleAgentIfNotContained(aAgent, aReason);
533 } else {
534 RemoveAudibleAgentIfContained(aAgent, aReason);
537 if (aAudible != AudibleState::eNotAudible) {
538 MaybeNotifyMediaBlockStart(aAgent);
542 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
543 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
544 MOZ_ASSERT(aAgent);
545 MOZ_ASSERT(mAgents.Contains(aAgent));
547 if (!mAudibleAgents.Contains(aAgent)) {
548 mAudibleAgents.AppendElement(aAgent);
549 if (IsFirstAudibleAgent()) {
550 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
551 aReason);
556 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
557 AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
558 MOZ_ASSERT(aAgent);
560 if (mAudibleAgents.Contains(aAgent)) {
561 mAudibleAgents.RemoveElement(aAgent);
562 if (IsLastAudibleAgent()) {
563 NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
564 aReason);
569 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
570 return (mAudibleAgents.Length() == 1);
573 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
574 return mAudibleAgents.IsEmpty();
577 void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
578 nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
579 AudibleChangedReasons aReason) {
580 RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
581 aWindow, aAudible == AudibleState::eAudible, aReason);
582 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
583 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
586 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
587 AudioChannelAgent* aAgent) {
588 nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
589 if (!window) {
590 return;
593 nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
594 if (!inner) {
595 return;
598 nsCOMPtr<Document> doc = inner->GetExtantDoc();
599 if (!doc) {
600 return;
603 if (!window->ShouldDelayMediaFromStart() || !doc->Hidden()) {
604 return;
607 if (!mShouldSendActiveMediaBlockStopEvent) {
608 mShouldSendActiveMediaBlockStopEvent = true;
609 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
610 "dom::AudioChannelService::AudioChannelWindow::"
611 "MaybeNotifyMediaBlockStart",
612 [window]() -> void {
613 nsCOMPtr<nsIObserverService> observerService =
614 services::GetObserverService();
615 if (NS_WARN_IF(!observerService)) {
616 return;
619 observerService->NotifyObservers(ToSupports(window), "audio-playback",
620 u"activeMediaBlockStart");
621 }));
625 } // namespace mozilla::dom