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->ShouldDelayMediaFromStart()) {
271       config.mSuspend = nsISuspendedTypes::SUSPENDED_BLOCK;
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 
NotifyResumingDelayedMedia(nsPIDOMWindowOuter * aWindow)449 void AudioChannelService::NotifyResumingDelayedMedia(
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   RefreshAgentsSuspend(aWindow, nsISuspendedTypes::NONE_SUSPENDED);
465 }
466 
AppendAgent(AudioChannelAgent * aAgent,AudibleState aAudible)467 void AudioChannelService::AudioChannelWindow::AppendAgent(
468     AudioChannelAgent* aAgent, AudibleState aAudible) {
469   MOZ_ASSERT(aAgent);
470 
471   AppendAgentAndIncreaseAgentsNum(aAgent);
472   AudioAudibleChanged(aAgent, aAudible,
473                       AudibleChangedReasons::eDataAudibleChanged);
474 }
475 
RemoveAgent(AudioChannelAgent * aAgent)476 void AudioChannelService::AudioChannelWindow::RemoveAgent(
477     AudioChannelAgent* aAgent) {
478   MOZ_ASSERT(aAgent);
479 
480   RemoveAgentAndReduceAgentsNum(aAgent);
481   AudioAudibleChanged(aAgent, AudibleState::eNotAudible,
482                       AudibleChangedReasons::ePauseStateChanged);
483 }
484 
NotifyMediaBlockStop(nsPIDOMWindowOuter * aWindow)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;
497           }
498 
499           observerService->NotifyObservers(ToSupports(window), "audio-playback",
500                                            u"activeMediaBlockStop");
501         }));
502   }
503 }
504 
AppendAgentAndIncreaseAgentsNum(AudioChannelAgent * aAgent)505 void AudioChannelService::AudioChannelWindow::AppendAgentAndIncreaseAgentsNum(
506     AudioChannelAgent* aAgent) {
507   MOZ_ASSERT(aAgent);
508   MOZ_ASSERT(!mAgents.Contains(aAgent));
509 
510   mAgents.AppendElement(aAgent);
511 
512   ++mConfig.mNumberOfAgents;
513 }
514 
RemoveAgentAndReduceAgentsNum(AudioChannelAgent * aAgent)515 void AudioChannelService::AudioChannelWindow::RemoveAgentAndReduceAgentsNum(
516     AudioChannelAgent* aAgent) {
517   MOZ_ASSERT(aAgent);
518   MOZ_ASSERT(mAgents.Contains(aAgent));
519 
520   mAgents.RemoveElement(aAgent);
521 
522   MOZ_ASSERT(mConfig.mNumberOfAgents > 0);
523   --mConfig.mNumberOfAgents;
524 }
525 
AudioAudibleChanged(AudioChannelAgent * aAgent,AudibleState aAudible,AudibleChangedReasons aReason)526 void AudioChannelService::AudioChannelWindow::AudioAudibleChanged(
527     AudioChannelAgent* aAgent, AudibleState aAudible,
528     AudibleChangedReasons aReason) {
529   MOZ_ASSERT(aAgent);
530 
531   if (aAudible == AudibleState::eAudible) {
532     AppendAudibleAgentIfNotContained(aAgent, aReason);
533   } else {
534     RemoveAudibleAgentIfContained(aAgent, aReason);
535   }
536 
537   if (aAudible != AudibleState::eNotAudible) {
538     MaybeNotifyMediaBlockStart(aAgent);
539   }
540 }
541 
AppendAudibleAgentIfNotContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)542 void AudioChannelService::AudioChannelWindow::AppendAudibleAgentIfNotContained(
543     AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
544   MOZ_ASSERT(aAgent);
545   MOZ_ASSERT(mAgents.Contains(aAgent));
546 
547   if (!mAudibleAgents.Contains(aAgent)) {
548     mAudibleAgents.AppendElement(aAgent);
549     if (IsFirstAudibleAgent()) {
550       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eAudible,
551                                 aReason);
552     }
553   }
554 }
555 
RemoveAudibleAgentIfContained(AudioChannelAgent * aAgent,AudibleChangedReasons aReason)556 void AudioChannelService::AudioChannelWindow::RemoveAudibleAgentIfContained(
557     AudioChannelAgent* aAgent, AudibleChangedReasons aReason) {
558   MOZ_ASSERT(aAgent);
559 
560   if (mAudibleAgents.Contains(aAgent)) {
561     mAudibleAgents.RemoveElement(aAgent);
562     if (IsLastAudibleAgent()) {
563       NotifyAudioAudibleChanged(aAgent->Window(), AudibleState::eNotAudible,
564                                 aReason);
565     }
566   }
567 }
568 
IsFirstAudibleAgent() const569 bool AudioChannelService::AudioChannelWindow::IsFirstAudibleAgent() const {
570   return (mAudibleAgents.Length() == 1);
571 }
572 
IsLastAudibleAgent() const573 bool AudioChannelService::AudioChannelWindow::IsLastAudibleAgent() const {
574   return mAudibleAgents.IsEmpty();
575 }
576 
NotifyAudioAudibleChanged(nsPIDOMWindowOuter * aWindow,AudibleState aAudible,AudibleChangedReasons aReason)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");
584 }
585 
MaybeNotifyMediaBlockStart(AudioChannelAgent * aAgent)586 void AudioChannelService::AudioChannelWindow::MaybeNotifyMediaBlockStart(
587     AudioChannelAgent* aAgent) {
588   nsCOMPtr<nsPIDOMWindowOuter> window = aAgent->Window();
589   if (!window) {
590     return;
591   }
592 
593   nsCOMPtr<nsPIDOMWindowInner> inner = window->GetCurrentInnerWindow();
594   if (!inner) {
595     return;
596   }
597 
598   nsCOMPtr<Document> doc = inner->GetExtantDoc();
599   if (!doc) {
600     return;
601   }
602 
603   if (!window->ShouldDelayMediaFromStart() || !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