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/. */
6 
7 #include "AudioChannelService.h"
8 
9 #include "base/basictypes.h"
10 
11 #include "mozilla/Services.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/Unused.h"
14 #include "mozilla/dom/Document.h"
15 
16 #include "nsContentUtils.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsThreadUtils.h"
19 #include "nsHashPropertyBag.h"
20 #include "nsComponentManagerUtils.h"
21 #include "nsGlobalWindow.h"
22 #include "nsPIDOMWindow.h"
23 #include "nsServiceManagerUtils.h"
24 
25 #include "mozilla/Preferences.h"
26 
27 using namespace mozilla;
28 using namespace mozilla::dom;
29 
30 mozilla::LazyLogModule gAudioChannelLog("AudioChannel");
31 
32 namespace {
33 
34 bool sXPCOMShuttingDown = false;
35 
36 class AudioPlaybackRunnable final : public Runnable {
37  public:
AudioPlaybackRunnable(nsPIDOMWindowOuter * aWindow,bool aActive,AudioChannelService::AudibleChangedReasons aReason)38   AudioPlaybackRunnable(nsPIDOMWindowOuter* aWindow, bool aActive,
39                         AudioChannelService::AudibleChangedReasons aReason)
40       : mozilla::Runnable("AudioPlaybackRunnable"),
41         mWindow(aWindow),
42         mActive(aActive),
43         mReason(aReason) {}
44 
Run()45   NS_IMETHOD Run() override {
46     nsCOMPtr<nsIObserverService> observerService =
47         services::GetObserverService();
48     if (NS_WARN_IF(!observerService)) {
49       return NS_ERROR_FAILURE;
50     }
51 
52     nsAutoString state;
53     GetActiveState(state);
54 
55     observerService->NotifyObservers(ToSupports(mWindow), "audio-playback",
56                                      state.get());
57 
58     MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
59             ("AudioPlaybackRunnable, active = %s, reason = %s\n",
60              mActive ? "true" : "false", AudibleChangedReasonToStr(mReason)));
61 
62     return NS_OK;
63   }
64 
65  private:
GetActiveState(nsAString & aState)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");
75       }
76     }
77   }
78 
79   nsCOMPtr<nsPIDOMWindowOuter> mWindow;
80   bool mActive;
81   AudioChannelService::AudibleChangedReasons mReason;
82 };
83 
84 }  // anonymous namespace
85 
86 namespace mozilla::dom {
87 
SuspendTypeToStr(const nsSuspendedTypes & aSuspend)88 const char* SuspendTypeToStr(const nsSuspendedTypes& aSuspend) {
89   MOZ_ASSERT(aSuspend == nsISuspendedTypes::NONE_SUSPENDED ||
90              aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK);
91 
92   switch (aSuspend) {
93     case nsISuspendedTypes::NONE_SUSPENDED:
94       return "none";
95     case nsISuspendedTypes::SUSPENDED_BLOCK:
96       return "block";
97     default:
98       return "unknown";
99   }
100 }
101 
AudibleStateToStr(const AudioChannelService::AudibleState & aAudible)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);
107 
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";
117   }
118 }
119 
AudibleChangedReasonToStr(const AudioChannelService::AudibleChangedReasons & aReason)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);
128 
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";
138   }
139 }
140 
141 StaticRefPtr<AudioChannelService> gAudioChannelService;
142 
143 /* static */
CreateServiceIfNeeded()144 void AudioChannelService::CreateServiceIfNeeded() {
145   MOZ_ASSERT(NS_IsMainThread());
146 
147   if (!gAudioChannelService) {
148     gAudioChannelService = new AudioChannelService();
149   }
150 }
151 
152 /* static */
GetOrCreate()153 already_AddRefed<AudioChannelService> AudioChannelService::GetOrCreate() {
154   if (sXPCOMShuttingDown) {
155     return nullptr;
156   }
157 
158   CreateServiceIfNeeded();
159   RefPtr<AudioChannelService> service = gAudioChannelService.get();
160   return service.forget();
161 }
162 
163 /* static */
Get()164 already_AddRefed<AudioChannelService> AudioChannelService::Get() {
165   if (sXPCOMShuttingDown) {
166     return nullptr;
167   }
168 
169   RefPtr<AudioChannelService> service = gAudioChannelService.get();
170   return service.forget();
171 }
172 
173 /* static */
GetAudioChannelLog()174 LogModule* AudioChannelService::GetAudioChannelLog() {
175   return gAudioChannelLog;
176 }
177 
178 /* static */
Shutdown()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");
185     }
186 
187     gAudioChannelService->mWindows.Clear();
188 
189     gAudioChannelService = nullptr;
190   }
191 }
192 
193 NS_INTERFACE_MAP_BEGIN(AudioChannelService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIObserver)194   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
195   NS_INTERFACE_MAP_ENTRY(nsIObserver)
196 NS_INTERFACE_MAP_END
197 
198 NS_IMPL_ADDREF(AudioChannelService)
199 NS_IMPL_RELEASE(AudioChannelService)
200 
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);
206   }
207 }
208 
209 AudioChannelService::~AudioChannelService() = default;
210 
RegisterAudioChannelAgent(AudioChannelAgent * aAgent,AudibleState aAudible)211 void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
212                                                     AudibleState aAudible) {
213   MOZ_ASSERT(aAgent);
214 
215   uint64_t windowID = aAgent->WindowID();
216   AudioChannelWindow* winData = GetWindowData(windowID);
217   if (!winData) {
218     winData = new AudioChannelWindow(windowID);
219     mWindows.AppendElement(WrapUnique(winData));
220   }
221 
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);
227 }
228 
UnregisterAudioChannelAgent(AudioChannelAgent * aAgent)229 void AudioChannelService::UnregisterAudioChannelAgent(
230     AudioChannelAgent* aAgent) {
231   MOZ_ASSERT(aAgent);
232 
233   AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
234   if (!winData) {
235     return;
236   }
237 
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);
243 }
244 
GetMediaConfig(nsPIDOMWindowOuter * aWindow) const245 AudioPlaybackConfig AudioChannelService::GetMediaConfig(
246     nsPIDOMWindowOuter* aWindow) const {
247   AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
248 
249   if (!aWindow) {
250     config.mVolume = 0.0;
251     config.mMuted = true;
252     config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
253     return config;
254   }
255 
256   AudioChannelWindow* winData = nullptr;
257   nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
258 
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;
267     }
268 
269     config.mMuted = config.mMuted || window->GetAudioMuted();
270     if (window->GetMediaSuspend() != nsISuspendedTypes::NONE_SUSPENDED) {
271       config.mSuspend = window->GetMediaSuspend();
272     }
273 
274     nsCOMPtr<nsPIDOMWindowOuter> win =
275         window->GetInProcessScriptableParentOrNull();
276     if (!win) {
277       break;
278     }
279 
280     window = win;
281 
282     // If there is no parent, or we are the toplevel we don't continue.
283   } while (window && window != aWindow);
284 
285   return config;
286 }
287 
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)288 void AudioChannelService::AudioAudibleChanged(AudioChannelAgent* aAgent,
289                                               AudibleState aAudible,
290                                               AudibleChangedReasons aReason) {
291   MOZ_ASSERT(aAgent);
292 
293   uint64_t windowID = aAgent->WindowID();
294   AudioChannelWindow* winData = GetWindowData(windowID);
295   if (winData) {
296     winData->AudioAudibleChanged(aAgent, aAudible, aReason);
297   }
298 }
299 
300 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)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);
309 
310     uint64_t outerID;
311     nsresult rv = wrapper->GetData(&outerID);
312     if (NS_WARN_IF(NS_FAILED(rv))) {
313       return rv;
314     }
315 
316     UniquePtr<AudioChannelWindow> winData;
317     {
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;
326         }
327       }
328     }
329 
330     if (winData) {
331       for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
332         agent->WindowVolumeChanged(winData->mConfig.mVolume,
333                                    winData->mConfig.mMuted);
334       }
335     }
336   }
337 
338   return NS_OK;
339 }
340 
RefreshAgents(nsPIDOMWindowOuter * aWindow,const std::function<void (AudioChannelAgent *)> & aFunc)341 void AudioChannelService::RefreshAgents(
342     nsPIDOMWindowOuter* aWindow,
343     const std::function<void(AudioChannelAgent*)>& aFunc) {
344   MOZ_ASSERT(aWindow);
345 
346   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
347   if (!topWindow) {
348     return;
349   }
350 
351   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
352   if (!winData) {
353     return;
354   }
355 
356   for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
357     aFunc(agent);
358   }
359 }
360 
RefreshAgentsVolume(nsPIDOMWindowOuter * aWindow,float aVolume,bool aMuted)361 void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindowOuter* aWindow,
362                                               float aVolume, bool aMuted) {
363   RefreshAgents(aWindow, [aVolume, aMuted](AudioChannelAgent* agent) {
364     agent->WindowVolumeChanged(aVolume, aMuted);
365   });
366 }
367 
RefreshAgentsSuspend(nsPIDOMWindowOuter * aWindow,nsSuspendedTypes aSuspend)368 void AudioChannelService::RefreshAgentsSuspend(nsPIDOMWindowOuter* aWindow,
369                                                nsSuspendedTypes aSuspend) {
370   RefreshAgents(aWindow, [aSuspend](AudioChannelAgent* agent) {
371     agent->WindowSuspendChanged(aSuspend);
372   });
373 }
374 
SetWindowAudioCaptured(nsPIDOMWindowOuter * aWindow,uint64_t aInnerWindowID,bool aCapture)375 void AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindowOuter* aWindow,
376                                                  uint64_t aInnerWindowID,
377                                                  bool aCapture) {
378   MOZ_ASSERT(NS_IsMainThread());
379   MOZ_ASSERT(aWindow);
380 
381   MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
382           ("AudioChannelService, SetWindowAudioCaptured, window = %p, "
383            "aCapture = %d\n",
384            aWindow, aCapture));
385 
386   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
387   if (!topWindow) {
388     return;
389   }
390 
391   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
392 
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;
400   }
401 
402   if (aCapture != winData->mIsAudioCaptured) {
403     winData->mIsAudioCaptured = aCapture;
404     for (AudioChannelAgent* agent : winData->mAgents.ForwardRange()) {
405       agent->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
406     }
407   }
408 }
409 
410 AudioChannelService::AudioChannelWindow*
GetOrCreateWindowData(nsPIDOMWindowOuter * aWindow)411 AudioChannelService::GetOrCreateWindowData(nsPIDOMWindowOuter* aWindow) {
412   MOZ_ASSERT(NS_IsMainThread());
413   MOZ_ASSERT(aWindow);
414 
415   AudioChannelWindow* winData = GetWindowData(aWindow->WindowID());
416   if (!winData) {
417     winData = new AudioChannelWindow(aWindow->WindowID());
418     mWindows.AppendElement(WrapUnique(winData));
419   }
420 
421   return winData;
422 }
423 
GetWindowData(uint64_t aWindowID) const424 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;
429   });
430   return foundIt != end ? foundIt->get() : nullptr;
431 }
432 
IsWindowActive(nsPIDOMWindowOuter * aWindow)433 bool AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow) {
434   MOZ_ASSERT(NS_IsMainThread());
435 
436   auto* window = nsPIDOMWindowOuter::From(aWindow)->GetInProcessScriptableTop();
437   if (!window) {
438     return false;
439   }
440 
441   AudioChannelWindow* winData = GetWindowData(window->WindowID());
442   if (!winData) {
443     return false;
444   }
445 
446   return !winData->mAudibleAgents.IsEmpty();
447 }
448 
NotifyMediaResumedFromBlock(nsPIDOMWindowOuter * aWindow)449 void AudioChannelService::NotifyMediaResumedFromBlock(
450     nsPIDOMWindowOuter* aWindow) {
451   MOZ_ASSERT(aWindow);
452 
453   nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetInProcessScriptableTop();
454   if (!topWindow) {
455     return;
456   }
457 
458   AudioChannelWindow* winData = GetWindowData(topWindow->WindowID());
459   if (!winData) {
460     return;
461   }
462 
463   winData->NotifyMediaBlockStop(aWindow);
464 }
465 
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)466 void AudioChannelService::AudioChannelWindow::AppendAgent(
467     AudioChannelAgent* aAgent, AudibleState aAudible) {
468   MOZ_ASSERT(aAgent);
469 
470   AppendAgentAndIncreaseAgentsNum(aAgent);
471   AudioAudibleChanged(aAgent, aAudible,
472                       AudibleChangedReasons::eDataAudibleChanged);
473 }
474 
RemoveAgent(AudioChannelAgent * aAgent)475 void AudioChannelService::AudioChannelWindow::RemoveAgent(
476     AudioChannelAgent* aAgent) {
477   MOZ_ASSERT(aAgent);
478 
479   RemoveAgentAndReduceAgentsNum(aAgent);
480   AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
481                       AudibleChangedReasons::ePauseStateChanged);
482 }
483 
NotifyMediaBlockStop(nsPIDOMWindowOuter * aWindow)484 void AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop(
485     nsPIDOMWindowOuter* aWindow) {
486   if (mShouldSendActiveMediaBlockStopEvent) {
487     mShouldSendActiveMediaBlockStopEvent = false;
488     nsCOMPtr<nsPIDOMWindowOuter> window = aWindow;
489     NS_DispatchToCurrentThread(NS_NewRunnableFunction(
490         "dom::AudioChannelService::AudioChannelWindow::NotifyMediaBlockStop",
491         [window]() -> void {
492           nsCOMPtr<nsIObserverService> observerService =
493               services::GetObserverService();
494           if (NS_WARN_IF(!observerService)) {
495             return;
496           }
497 
498           observerService->NotifyObservers(ToSupports(window), "audio-playback",
499                                            u"activeMediaBlockStop");
500         }));
501   }
502 }
503 
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)504 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
505     AudioChannelAgent* aAgent) {
506   MOZ_ASSERT(aAgent);
507   MOZ_ASSERT(!mAgents.Contains(aAgent));
508 
509   mAgents.AppendElement(aAgent);
510 
511   ++mConfig.mNumberOfAgents;
512 }
513 
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)514 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
515     AudioChannelAgent* aAgent) {
516   MOZ_ASSERT(aAgent);
517   MOZ_ASSERT(mAgents.Contains(aAgent));
518 
519   mAgents.RemoveElement(aAgent);
520 
521   MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
522   --mConfig.mNumberOfAgents;
523 }
524 
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)525 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
526     AudioChannelAgent* aAgent, AudibleState aAudible,
527     AudibleChangedReasons aReason) {
528   MOZ_ASSERT(aAgent);
529 
530   if (aAudible == AudibleState::eAudible) {
531     AppendAudibleAgentIfNotContained(aAgent, aReason);
532   } else {
533     RemoveAudibleAgentIfContained(aAgent, aReason);
534   }
535 
536   if (aAudible != AudibleState::eNotAudible) {
537     MaybeNotifyMediaBlockStart(aAgent);
538   }
539 }
540 
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)541 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
542     AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
543   MOZ_ASSERT(aAgent);
544   MOZ_ASSERT(mAgents.Contains(aAgent));
545 
546   if (!mAudibleAgents.Contains(aAgent)) {
547     mAudibleAgents.AppendElement(aAgent);
548     if (IsFirstAudibleAgent()) {
549       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
550                                 aReason);
551     }
552   }
553 }
554 
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)555 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
556     AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
557   MOZ_ASSERT(aAgent);
558 
559   if (mAudibleAgents.Contains(aAgent)) {
560     mAudibleAgents.RemoveElement(aAgent);
561     if (IsLastAudibleAgent()) {
562       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
563                                 aReason);
564     }
565   }
566 }
567 
IsFirstAudibleAgent() const568 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
569   return (mAudibleAgents.Length() == 1);
570 }
571 
IsLastAudibleAgent() const572 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
573   return mAudibleAgents.IsEmpty();
574 }
575 
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)576 void AudioChannelService::AudioChannelWindow::NotifyAudioAudibleChanged(
577     nsPIDOMWindowOuter* aWindow, AudibleState aAudible,
578     AudibleChangedReasons aReason) {
579   RefPtr<AudioPlaybackRunnable> runnable = new AudioPlaybackRunnable(
580       aWindow, aAudible == AudibleState::eAudible, aReason);
581   DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
582   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
583 }
584 
MaybeNotifyMediaBlockStart(AudioChannelAgent * aAgent)585 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
586     AudioChannelAgent* aAgent) {
587   nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
588   if (!window) {
589     return;
590   }
591 
592   nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
593   if (!inner) {
594     return;
595   }
596 
597   nsCOMPtr<Document> doc = inner->GetExtantDoc();
598   if (!doc) {
599     return;
600   }
601 
602   if (window->GetMediaSuspend() != nsISuspendedTypes::SUSPENDED_BLOCK ||
603       !doc->Hidden()) {
604     return;
605   }
606 
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;
617           }
618 
619           observerService->NotifyObservers(ToSupports(window), "audio-playback",
620                                            u"activeMediaBlockStart");
621         }));
622   }
623 }
624 
625 }  // namespace mozilla::dom
626