1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "AudioChannelAgent.h"
6 #include "AudioChannelService.h"
7 #include "mozilla/Preferences.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/Document.h"
10 #include "nsPIDOMWindow.h"
11 
12 using namespace mozilla::dom;
13 
14 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
15 
16 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioChannelAgent)
17   tmp->Shutdown();
18   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)19   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
21 
22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioChannelAgent)
23   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
24   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
25 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
26 
27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioChannelAgent)
28   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgent)
29   NS_INTERFACE_MAP_ENTRY(nsISupports)
30 NS_INTERFACE_MAP_END
31 
32 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
33 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
34 
35 AudioChannelAgent::AudioChannelAgent()
36     : mInnerWindowID(0), mIsRegToService(false) {
37   // Init service in the begining, it can help us to know whether there is any
38   // created media component via AudioChannelService::IsServiceStarted().
39   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
40 }
41 
~AudioChannelAgent()42 AudioChannelAgent::~AudioChannelAgent() { Shutdown(); }
43 
Shutdown()44 void AudioChannelAgent::Shutdown() {
45   if (mIsRegToService) {
46     NotifyStoppedPlaying();
47   }
48 }
49 
50 NS_IMETHODIMP
Init(mozIDOMWindow * aWindow,nsIAudioChannelAgentCallback * aCallback)51 AudioChannelAgent::Init(mozIDOMWindow* aWindow,
52                         nsIAudioChannelAgentCallback* aCallback) {
53   return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback,
54                       /* useWeakRef = */ false);
55 }
56 
57 NS_IMETHODIMP
InitWithWeakCallback(mozIDOMWindow * aWindow,nsIAudioChannelAgentCallback * aCallback)58 AudioChannelAgent::InitWithWeakCallback(
59     mozIDOMWindow* aWindow, nsIAudioChannelAgentCallback* aCallback) {
60   return InitInternal(nsPIDOMWindowInner::From(aWindow), aCallback,
61                       /* useWeakRef = */ true);
62 }
63 
FindCorrectWindow(nsPIDOMWindowInner * aWindow)64 nsresult AudioChannelAgent::FindCorrectWindow(nsPIDOMWindowInner* aWindow) {
65   mWindow = aWindow->GetInProcessScriptableTop();
66   if (NS_WARN_IF(!mWindow)) {
67     return NS_OK;
68   }
69 
70   // From here we do an hack for nested iframes.
71   // The system app doesn't have access to the nested iframe objects so it
72   // cannot control the volume of the agents running in nested apps. What we do
73   // here is to assign those Agents to the top scriptable window of the parent
74   // iframe (what is controlled by the system app).
75   // For doing this we go recursively back into the chain of windows until we
76   // find apps that are not the system one.
77   nsCOMPtr<nsPIDOMWindowOuter> outerParent = mWindow->GetInProcessParent();
78   if (!outerParent || outerParent == mWindow) {
79     return NS_OK;
80   }
81 
82   nsCOMPtr<nsPIDOMWindowInner> parent = outerParent->GetCurrentInnerWindow();
83   if (!parent) {
84     return NS_OK;
85   }
86 
87   nsCOMPtr<Document> doc = parent->GetExtantDoc();
88   if (!doc) {
89     return NS_OK;
90   }
91 
92   if (nsContentUtils::IsChromeDoc(doc)) {
93     return NS_OK;
94   }
95 
96   return FindCorrectWindow(parent);
97 }
98 
InitInternal(nsPIDOMWindowInner * aWindow,nsIAudioChannelAgentCallback * aCallback,bool aUseWeakRef)99 nsresult AudioChannelAgent::InitInternal(
100     nsPIDOMWindowInner* aWindow, nsIAudioChannelAgentCallback* aCallback,
101     bool aUseWeakRef) {
102   if (NS_WARN_IF(!aWindow)) {
103     return NS_ERROR_FAILURE;
104   }
105 
106   mInnerWindowID = aWindow->WindowID();
107 
108   nsresult rv = FindCorrectWindow(aWindow);
109   if (NS_WARN_IF(NS_FAILED(rv))) {
110     return rv;
111   }
112 
113   if (aUseWeakRef) {
114     mWeakCallback = do_GetWeakReference(aCallback);
115   } else {
116     mCallback = aCallback;
117   }
118 
119   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
120           ("AudioChannelAgent, InitInternal, this = %p, "
121            "owner = %p, hasCallback = %d\n",
122            this, mWindow.get(), (!!mCallback || !!mWeakCallback)));
123 
124   return NS_OK;
125 }
126 
PullInitialUpdate()127 void AudioChannelAgent::PullInitialUpdate() {
128   RefPtr<AudioChannelService> service = AudioChannelService::Get();
129   MOZ_ASSERT(service);
130   MOZ_ASSERT(mIsRegToService);
131 
132   AudioPlaybackConfig config = service->GetMediaConfig(mWindow);
133   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
134           ("AudioChannelAgent, PullInitialUpdate, this=%p, "
135            "mute=%s, volume=%f, suspend=%s, audioCapturing=%s\n",
136            this, config.mMuted ? "true" : "false", config.mVolume,
137            SuspendTypeToStr(config.mSuspend),
138            config.mCapturedAudio ? "true" : "false"));
139   WindowVolumeChanged(config.mVolume, config.mMuted);
140   WindowSuspendChanged(config.mSuspend);
141   WindowAudioCaptureChanged(InnerWindowID(), config.mCapturedAudio);
142 }
143 
144 NS_IMETHODIMP
NotifyStartedPlaying(uint8_t aAudible)145 AudioChannelAgent::NotifyStartedPlaying(uint8_t aAudible) {
146   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
147   if (service == nullptr || mIsRegToService) {
148     return NS_ERROR_FAILURE;
149   }
150 
151   MOZ_ASSERT(AudioChannelService::AudibleState::eNotAudible == 0 &&
152              AudioChannelService::AudibleState::eMaybeAudible == 1 &&
153              AudioChannelService::AudibleState::eAudible == 2);
154   service->RegisterAudioChannelAgent(
155       this, static_cast<AudioChannelService::AudibleState>(aAudible));
156 
157   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
158           ("AudioChannelAgent, NotifyStartedPlaying, this = %p, audible = %s\n",
159            this,
160            AudibleStateToStr(
161                static_cast<AudioChannelService::AudibleState>(aAudible))));
162 
163   mIsRegToService = true;
164   return NS_OK;
165 }
166 
167 NS_IMETHODIMP
NotifyStoppedPlaying()168 AudioChannelAgent::NotifyStoppedPlaying() {
169   if (!mIsRegToService) {
170     return NS_ERROR_FAILURE;
171   }
172 
173   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
174           ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this));
175 
176   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
177   if (service) {
178     service->UnregisterAudioChannelAgent(this);
179   }
180 
181   mIsRegToService = false;
182   return NS_OK;
183 }
184 
185 NS_IMETHODIMP
NotifyStartedAudible(uint8_t aAudible,uint32_t aReason)186 AudioChannelAgent::NotifyStartedAudible(uint8_t aAudible, uint32_t aReason) {
187   MOZ_LOG(
188       AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
189       ("AudioChannelAgent, NotifyStartedAudible, this = %p, "
190        "audible = %s, reason = %s\n",
191        this,
192        AudibleStateToStr(
193            static_cast<AudioChannelService::AudibleState>(aAudible)),
194        AudibleChangedReasonToStr(
195            static_cast<AudioChannelService::AudibleChangedReasons>(aReason))));
196 
197   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
198   if (NS_WARN_IF(!service)) {
199     return NS_ERROR_FAILURE;
200   }
201 
202   service->AudioAudibleChanged(
203       this, static_cast<AudioChannelService::AudibleState>(aAudible),
204       static_cast<AudioChannelService::AudibleChangedReasons>(aReason));
205   return NS_OK;
206 }
207 
208 already_AddRefed<nsIAudioChannelAgentCallback>
GetCallback()209 AudioChannelAgent::GetCallback() {
210   nsCOMPtr<nsIAudioChannelAgentCallback> callback = mCallback;
211   if (!callback) {
212     callback = do_QueryReferent(mWeakCallback);
213   }
214   return callback.forget();
215 }
216 
WindowVolumeChanged(float aVolume,bool aMuted)217 void AudioChannelAgent::WindowVolumeChanged(float aVolume, bool aMuted) {
218   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
219   if (!callback) {
220     return;
221   }
222 
223   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
224           ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %s, "
225            "volume = %f\n",
226            this, aMuted ? "true" : "false", aVolume));
227   callback->WindowVolumeChanged(aVolume, aMuted);
228 }
229 
WindowSuspendChanged(nsSuspendedTypes aSuspend)230 void AudioChannelAgent::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
231   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
232   if (!callback) {
233     return;
234   }
235 
236   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
237           ("AudioChannelAgent, WindowSuspendChanged, this = %p, "
238            "suspended = %s\n",
239            this, SuspendTypeToStr(aSuspend)));
240   callback->WindowSuspendChanged(aSuspend);
241 }
242 
GetMediaConfig() const243 AudioPlaybackConfig AudioChannelAgent::GetMediaConfig() const {
244   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
245   AudioPlaybackConfig config(1.0, false, nsISuspendedTypes::NONE_SUSPENDED);
246   if (service) {
247     config = service->GetMediaConfig(mWindow);
248   }
249   return config;
250 }
251 
WindowID() const252 uint64_t AudioChannelAgent::WindowID() const {
253   return mWindow ? mWindow->WindowID() : 0;
254 }
255 
InnerWindowID() const256 uint64_t AudioChannelAgent::InnerWindowID() const { return mInnerWindowID; }
257 
WindowAudioCaptureChanged(uint64_t aInnerWindowID,bool aCapture)258 void AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID,
259                                                   bool aCapture) {
260   if (aInnerWindowID != mInnerWindowID) {
261     return;
262   }
263 
264   nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
265   if (!callback) {
266     return;
267   }
268 
269   MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
270           ("AudioChannelAgent, WindowAudioCaptureChanged, this = %p, "
271            "capture = %d\n",
272            this, aCapture));
273 
274   callback->WindowAudioCaptureChanged(aCapture);
275 }
276 
IsWindowAudioCapturingEnabled() const277 bool AudioChannelAgent::IsWindowAudioCapturingEnabled() const {
278   return GetMediaConfig().mCapturedAudio;
279 }
280 
IsPlayingStarted() const281 bool AudioChannelAgent::IsPlayingStarted() const { return mIsRegToService; }
282