1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
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 "nscore.h"
8 #include "plstr.h"
9 #include <stdio.h>
10 #include "nsString.h"
11 #include <windows.h>
12 
13 // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
14 #include <mmsystem.h>
15 
16 #include "HeadlessSound.h"
17 #include "nsSound.h"
18 #include "nsIURL.h"
19 #include "nsNetUtil.h"
20 #include "nsIChannel.h"
21 #include "nsContentUtils.h"
22 #include "nsCRT.h"
23 #include "nsIObserverService.h"
24 
25 #include "mozilla/Logging.h"
26 #include "prtime.h"
27 
28 #include "nsNativeCharsetUtils.h"
29 #include "nsThreadUtils.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "gfxPlatform.h"
32 
33 using mozilla::LogLevel;
34 
35 static mozilla::LazyLogModule gWin32SoundLog("nsSound");
36 
37 class nsSoundPlayer : public mozilla::Runnable {
38  public:
nsSoundPlayer(const nsAString & aSoundName)39   explicit nsSoundPlayer(const nsAString &aSoundName)
40       : mozilla::Runnable("nsSoundPlayer"),
41         mSoundName(aSoundName),
42         mSoundData(nullptr) {}
43 
nsSoundPlayer(const uint8_t * aData,size_t aSize)44   nsSoundPlayer(const uint8_t *aData, size_t aSize)
45       : mozilla::Runnable("nsSoundPlayer"), mSoundName(EmptyString()) {
46     MOZ_ASSERT(aSize > 0, "Size should not be zero");
47     MOZ_ASSERT(aData, "Data shoud not be null");
48 
49     // We will disptach nsSoundPlayer to playerthread, so keep a data copy
50     mSoundData = new uint8_t[aSize];
51     memcpy(mSoundData, aData, aSize);
52   }
53 
54   NS_DECL_NSIRUNNABLE
55 
56  protected:
57   ~nsSoundPlayer();
58 
59   nsString mSoundName;
60   uint8_t *mSoundData;
61 };
62 
63 NS_IMETHODIMP
Run()64 nsSoundPlayer::Run() {
65   MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData,
66              "Sound name or sound data should be specified");
67   DWORD flags = SND_NODEFAULT | SND_ASYNC;
68 
69   if (mSoundData) {
70     flags |= SND_MEMORY;
71     ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags);
72   } else {
73     flags |= SND_ALIAS;
74     ::PlaySoundW(mSoundName.get(), nullptr, flags);
75   }
76   return NS_OK;
77 }
78 
~nsSoundPlayer()79 nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; }
80 
81 mozilla::StaticRefPtr<nsISound> nsSound::sInstance;
82 
GetInstance()83 /* static */ already_AddRefed<nsISound> nsSound::GetInstance() {
84   if (!sInstance) {
85     if (gfxPlatform::IsHeadless()) {
86       sInstance = new mozilla::widget::HeadlessSound();
87     } else {
88       RefPtr<nsSound> sound = new nsSound();
89       nsresult rv = sound->CreatePlayerThread();
90       if (NS_WARN_IF(NS_FAILED(rv))) {
91         return nullptr;
92       }
93       sInstance = sound.forget();
94     }
95     ClearOnShutdown(&sInstance);
96   }
97 
98   RefPtr<nsISound> service = sInstance;
99   return service.forget();
100 }
101 
102 #ifndef SND_PURGE
103 // Not available on Windows CE, and according to MSDN
104 // doesn't do anything on recent windows either.
105 #define SND_PURGE 0
106 #endif
107 
NS_IMPL_ISUPPORTS(nsSound,nsISound,nsIStreamLoaderObserver)108 NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
109 
110 nsSound::nsSound() : mInited(false) {}
111 
~nsSound()112 nsSound::~nsSound() {}
113 
PurgeLastSound()114 void nsSound::PurgeLastSound() {
115   // Halt any currently playing sound.
116   if (mSoundPlayer) {
117     if (mPlayerThread) {
118       mPlayerThread->Dispatch(NS_NewRunnableFunction(
119         "nsSound::PurgeLastSound", [player = std::move(mSoundPlayer)]() {
120           // Capture move mSoundPlayer to lambda then
121           // PlaySoundW(nullptr, nullptr, SND_PURGE) will be called before
122           // freeing the nsSoundPlayer.
123           ::PlaySoundW(nullptr, nullptr, SND_PURGE);
124         }), NS_DISPATCH_NORMAL);
125     }
126   }
127 }
128 
Beep()129 NS_IMETHODIMP nsSound::Beep() {
130   ::MessageBeep(0);
131 
132   return NS_OK;
133 }
134 
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * context,nsresult aStatus,uint32_t dataLen,const uint8_t * data)135 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
136                                         nsISupports *context, nsresult aStatus,
137                                         uint32_t dataLen, const uint8_t *data) {
138   MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
139   // print a load error on bad status
140   if (NS_FAILED(aStatus)) {
141 #ifdef DEBUG
142     if (aLoader) {
143       nsCOMPtr<nsIRequest> request;
144       nsCOMPtr<nsIChannel> channel;
145       aLoader->GetRequest(getter_AddRefs(request));
146       if (request) channel = do_QueryInterface(request);
147       if (channel) {
148         nsCOMPtr<nsIURI> uri;
149         channel->GetURI(getter_AddRefs(uri));
150         if (uri) {
151           nsAutoCString uriSpec;
152           uri->GetSpec(uriSpec);
153           MOZ_LOG(gWin32SoundLog, LogLevel::Info,
154                   ("Failed to load %s\n", uriSpec.get()));
155         }
156       }
157     }
158 #endif
159     return aStatus;
160   }
161 
162   PurgeLastSound();
163 
164   if (data && dataLen > 0) {
165     MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
166     mSoundPlayer = new nsSoundPlayer(data, dataLen);
167     MOZ_ASSERT(mSoundPlayer, "Could not create player");
168 
169     nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
170     if (NS_WARN_IF(FAILED(rv))) {
171       return rv;
172     }
173   }
174 
175   return NS_OK;
176 }
177 
Play(nsIURL * aURL)178 NS_IMETHODIMP nsSound::Play(nsIURL *aURL) {
179   nsresult rv;
180 
181 #ifdef DEBUG_SOUND
182   char *url;
183   aURL->GetSpec(&url);
184   MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url));
185 #endif
186 
187   nsCOMPtr<nsIStreamLoader> loader;
188   rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL,
189                           this,  // aObserver
190                           nsContentUtils::GetSystemPrincipal(),
191                           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
192                           nsIContentPolicy::TYPE_OTHER);
193   return rv;
194 }
195 
CreatePlayerThread()196 nsresult nsSound::CreatePlayerThread() {
197   if (mPlayerThread) {
198     return NS_OK;
199   }
200   if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
201                                              getter_AddRefs(mPlayerThread))))) {
202     return NS_ERROR_FAILURE;
203   }
204 
205   // Add an observer for shutdown event to release the thread at that time
206   nsCOMPtr<nsIObserverService> observerService =
207       mozilla::services::GetObserverService();
208   if (!observerService) {
209     return NS_ERROR_FAILURE;
210   }
211 
212   observerService->AddObserver(this, "xpcom-shutdown-threads", false);
213   return NS_OK;
214 }
215 
216 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)217 nsSound::Observe(nsISupports *aSubject, const char *aTopic,
218                  const char16_t *aData) {
219   if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
220     PurgeLastSound();
221 
222     if (mPlayerThread) {
223       mPlayerThread->Shutdown();
224       mPlayerThread = nullptr;
225     }
226   }
227 
228   return NS_OK;
229 }
230 
Init()231 NS_IMETHODIMP nsSound::Init() {
232   if (mInited) {
233     return NS_OK;
234   }
235 
236   MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
237   // This call halts a sound if it was still playing.
238   // We have to use the sound library for something to make sure
239   // it is initialized.
240   // If we wait until the first sound is played, there will
241   // be a time lag as the library gets loaded.
242   // This should be done in player thread otherwise it will block main thread
243   // at the first time loading sound library.
244   mPlayerThread->Dispatch(
245       NS_NewRunnableFunction(
246           "nsSound::Init", []() { ::PlaySoundW(nullptr, nullptr, SND_PURGE); }),
247       NS_DISPATCH_NORMAL);
248 
249   mInited = true;
250 
251   return NS_OK;
252 }
253 
PlaySystemSound(const nsAString & aSoundAlias)254 NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) {
255   MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
256   PurgeLastSound();
257 
258   if (!NS_IsMozAliasSound(aSoundAlias)) {
259     if (aSoundAlias.IsEmpty()) return NS_OK;
260     nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(aSoundAlias);
261     MOZ_ASSERT(player, "Could not create player");
262     nsresult rv = mPlayerThread->Dispatch(player, NS_DISPATCH_NORMAL);
263     if (NS_WARN_IF(NS_FAILED(rv))) {
264       return rv;
265     }
266     return NS_OK;
267   }
268 
269   NS_WARNING(
270       "nsISound::playSystemSound is called with \"_moz_\" events, they are "
271       "obsolete, use nsISound::playEventSound instead");
272 
273   uint32_t eventId;
274   if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
275     eventId = EVENT_NEW_MAIL_RECEIVED;
276   else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
277     eventId = EVENT_CONFIRM_DIALOG_OPEN;
278   else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
279     eventId = EVENT_ALERT_DIALOG_OPEN;
280   else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
281     eventId = EVENT_MENU_EXECUTE;
282   else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
283     eventId = EVENT_MENU_POPUP;
284   else
285     return NS_OK;
286 
287   return PlayEventSound(eventId);
288 }
289 
PlayEventSound(uint32_t aEventId)290 NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
291   MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
292   PurgeLastSound();
293 
294   const wchar_t *sound = nullptr;
295   switch (aEventId) {
296     case EVENT_NEW_MAIL_RECEIVED:
297       sound = L"MailBeep";
298       break;
299     case EVENT_ALERT_DIALOG_OPEN:
300       sound = L"SystemExclamation";
301       break;
302     case EVENT_CONFIRM_DIALOG_OPEN:
303       sound = L"SystemQuestion";
304       break;
305     case EVENT_MENU_EXECUTE:
306       sound = L"MenuCommand";
307       break;
308     case EVENT_MENU_POPUP:
309       sound = L"MenuPopup";
310       break;
311     case EVENT_EDITOR_MAX_LEN:
312       sound = L".Default";
313       break;
314     default:
315       // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
316       // NS_SYSSOUND_SELECT_DIALOG.
317       return NS_OK;
318   }
319   NS_ASSERTION(sound, "sound is null");
320   MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
321   mSoundPlayer = new nsSoundPlayer(nsDependentString(sound));
322   MOZ_ASSERT(mSoundPlayer, "Could not create player");
323   nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
324   if (NS_WARN_IF(NS_FAILED(rv))) {
325     return rv;
326   }
327   return NS_OK;
328 }
329